Merge branch 'client_enhancements'
This commit is contained in:
commit
d6293cf9b3
|
@ -19,11 +19,14 @@ cache:
|
|||
|
||||
install: /bin/true
|
||||
|
||||
# This seems to be required to get travis to set Xmx4g, per https://github.com/travis-ci/travis-ci/issues/3893
|
||||
before_script:
|
||||
# This seems to be required to get travis to set Xmx4g, per https://github.com/travis-ci/travis-ci/issues/3893
|
||||
- export MAVEN_SKIP_RC=true
|
||||
# Sometimes things get restored from the cache with bad permissions. See https://github.com/travis-ci/travis-ci/issues/9630
|
||||
- sudo chmod -R 777 "$HOME/.m2/repository";
|
||||
- sudo chown -R travis:travis "$HOME/.m2/repository";
|
||||
|
||||
script:
|
||||
# - mvn -e -B clean install && cd hapi-fhir-ra && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID clean test jacoco:report coveralls:report
|
||||
# - mvn -Dci=true -e -B -P ALLMODULES,NOPARALLEL,ERRORPRONE clean install && cd hapi-fhir-jacoco && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID jacoco:report coveralls:report
|
||||
- mvn -Dci=true -e -B -P ALLMODULES,REDUCED_JPA_TESTS,ERRORPRONE,JACOCO clean install && cd hapi-fhir-jacoco && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID jacoco:report coveralls:report
|
||||
- mvn -Dci=true -e -B -P ALLMODULES,REDUCED_JPA_TESTS,ERRORPRONE,JACOCO clean install && cd hapi-fhir-jacoco && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID jacoco:report coveralls:report;
|
||||
|
|
|
@ -130,7 +130,7 @@ public class Constants {
|
|||
/**
|
||||
* Used in paging links
|
||||
*/
|
||||
public static final Object PARAM_BUNDLETYPE = "_bundletype";
|
||||
public static final String PARAM_BUNDLETYPE = "_bundletype";
|
||||
public static final String PARAM_CONTENT = "_content";
|
||||
public static final String PARAM_COUNT = "_count";
|
||||
public static final String PARAM_DELETE = "_delete";
|
||||
|
@ -140,7 +140,7 @@ public class Constants {
|
|||
public static final String PARAM_HISTORY = "_history";
|
||||
public static final String PARAM_INCLUDE = "_include";
|
||||
public static final String PARAM_INCLUDE_QUALIFIER_RECURSE = ":recurse";
|
||||
public static final String PARAM_INCLUDE_RECURSE = "_include"+PARAM_INCLUDE_QUALIFIER_RECURSE;
|
||||
public static final String PARAM_INCLUDE_RECURSE = "_include" + PARAM_INCLUDE_QUALIFIER_RECURSE;
|
||||
public static final String PARAM_LASTUPDATED = "_lastUpdated";
|
||||
public static final String PARAM_NARRATIVE = "_narrative";
|
||||
public static final String PARAM_PAGINGACTION = "_getpages";
|
||||
|
@ -152,7 +152,7 @@ public class Constants {
|
|||
public static final String PARAM_QUERY = "_query";
|
||||
public static final String PARAM_RESPONSE_URL = "response-url"; //Used in messaging
|
||||
public static final String PARAM_REVINCLUDE = "_revinclude";
|
||||
public static final String PARAM_REVINCLUDE_RECURSE = PARAM_REVINCLUDE+PARAM_INCLUDE_QUALIFIER_RECURSE;
|
||||
public static final String PARAM_REVINCLUDE_RECURSE = PARAM_REVINCLUDE + PARAM_INCLUDE_QUALIFIER_RECURSE;
|
||||
public static final String PARAM_SEARCH = "_search";
|
||||
public static final String PARAM_SECURITY = "_security";
|
||||
public static final String PARAM_SINCE = "_since";
|
||||
|
@ -160,9 +160,9 @@ public class Constants {
|
|||
public static final String PARAM_SORT_ASC = "_sort:asc";
|
||||
public static final String PARAM_SORT_DESC = "_sort:desc";
|
||||
public static final String PARAM_SUMMARY = "_summary";
|
||||
public static final String PARAM_TAG = "_tag";
|
||||
public static final String PARAM_TAGS = "_tags";
|
||||
public static final String PARAM_TEXT = "_text";
|
||||
public static final String PARAM_TAG = "_tag";
|
||||
public static final String PARAM_TAGS = "_tags";
|
||||
public static final String PARAM_TEXT = "_text";
|
||||
public static final String PARAM_VALIDATE = "_validate";
|
||||
public static final String PARAMQUALIFIER_MISSING = ":missing";
|
||||
public static final String PARAMQUALIFIER_MISSING_FALSE = "false";
|
||||
|
@ -195,9 +195,12 @@ public class Constants {
|
|||
public static final String HEADER_X_CACHE = "X-Cache";
|
||||
public static final String HEADER_X_SECURITY_CONTEXT = "X-Security-Context";
|
||||
public static final String POWERED_BY_HEADER = "X-Powered-By";
|
||||
public static final Charset CHARSET_US_ASCII;
|
||||
public static final String PARAM_PAGEID = "_pageId";
|
||||
|
||||
static {
|
||||
CHARSET_UTF8 = Charset.forName(CHARSET_NAME_UTF8);
|
||||
CHARSET_US_ASCII = Charset.forName("ISO-8859-1");
|
||||
|
||||
HashMap<Integer, String> statusNames = new HashMap<>();
|
||||
statusNames.put(200, "OK");
|
||||
|
|
|
@ -34,36 +34,35 @@ public interface IHttpRequest {
|
|||
* @param theName the header name
|
||||
* @param theValue the header value
|
||||
*/
|
||||
public void addHeader(String theName, String theValue);
|
||||
void addHeader(String theName, String theValue);
|
||||
|
||||
/**
|
||||
* Execute the request
|
||||
* @return the response
|
||||
* @throws IOException
|
||||
*/
|
||||
public IHttpResponse execute() throws IOException;
|
||||
IHttpResponse execute() throws IOException;
|
||||
|
||||
/**
|
||||
* @return all request headers in lower case
|
||||
* @return all request headers in lower case. Note that this method
|
||||
* returns an <b>immutable</b> Map
|
||||
*/
|
||||
public Map<String, List<String>> getAllHeaders();
|
||||
Map<String, List<String>> getAllHeaders();
|
||||
|
||||
/**
|
||||
* Return the requestbody as a string.
|
||||
* Return the request body as a string.
|
||||
* If this is not supported by the underlying technology, null is returned
|
||||
* @return a string representation of the request or null if not supported or empty.
|
||||
* @throws IOException
|
||||
*/
|
||||
public String getRequestBodyFromStream() throws IOException;
|
||||
String getRequestBodyFromStream() throws IOException;
|
||||
|
||||
/**
|
||||
* Return the request URI, or null
|
||||
*/
|
||||
public String getUri();
|
||||
String getUri();
|
||||
|
||||
/**
|
||||
* Return the HTTP verb (e.g. "GET")
|
||||
*/
|
||||
public String getHttpVerbName();
|
||||
String getHttpVerbName();
|
||||
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package ca.uhn.fhir.rest.gclient;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
|
@ -26,10 +27,32 @@ import ca.uhn.fhir.model.api.IQueryParameterType;
|
|||
|
||||
public interface IBaseQuery<T extends IBaseQuery<?>> {
|
||||
|
||||
T where(ICriterion<?> theCriterion);
|
||||
/**
|
||||
* Add a search parameter to the query.
|
||||
* <p>
|
||||
* Note that this method is a synonym for {@link #where(ICriterion)}, and is only
|
||||
* here to make fluent queries read more naturally.
|
||||
* </p>
|
||||
*/
|
||||
T and(ICriterion<?> theCriterion);
|
||||
|
||||
T where(Map<String, List<IQueryParameterType>> theCriterion);
|
||||
/**
|
||||
* Add a set of search parameters to the query.
|
||||
*/
|
||||
T where(Map<String, List<IQueryParameterType>> theCriterion);
|
||||
|
||||
T and(ICriterion<?> theCriterion);
|
||||
/**
|
||||
* Add a search parameter to the query.
|
||||
*/
|
||||
T where(ICriterion<?> theCriterion);
|
||||
|
||||
/**
|
||||
* Add a set of search parameters to the query.
|
||||
* <p>
|
||||
* Values will be treated semi-literally. No FHIR escaping will be performed
|
||||
* on the values, but regular URL escaping will be.
|
||||
* </p>
|
||||
*/
|
||||
T whereMap(Map<String, List<String>> theRawMap);
|
||||
|
||||
}
|
||||
|
|
|
@ -20,16 +20,23 @@ package ca.uhn.fhir.rest.gclient;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
public interface IHistoryTyped<T> extends IClientExecutable<IHistoryTyped<T>, T> {
|
||||
import java.util.Date;
|
||||
|
||||
public interface IHistoryTyped<T> extends IClientExecutable<IHistoryTyped<T>, T> {
|
||||
|
||||
/**
|
||||
* Request that the server return only resource versions that were created at or after the given time (inclusive)
|
||||
* Request that the server return only the history elements between the
|
||||
* specific range
|
||||
*/
|
||||
IHistoryTyped<T> since(Date theCutoff);
|
||||
IHistoryTyped<T> at(DateRangeParam theDateRangeParam);
|
||||
|
||||
/**
|
||||
* Request that the server return only up to <code>theCount</code> number of resources
|
||||
*/
|
||||
IHistoryTyped<T> count(Integer theCount);
|
||||
|
||||
/**
|
||||
* Request that the server return only resource versions that were created at or after the given time (inclusive)
|
||||
|
@ -41,9 +48,9 @@ public interface IHistoryTyped<T> extends IClientExecutable<IHistoryTyped<T>, T>
|
|||
IHistoryTyped<T> since(IPrimitiveType<Date> theCutoff);
|
||||
|
||||
/**
|
||||
* Request that the server return only up to <code>theCount</code> number of resources
|
||||
* Request that the server return only resource versions that were created at or after the given time (inclusive)
|
||||
*/
|
||||
IHistoryTyped<T> count(Integer theCount);
|
||||
IHistoryTyped<T> since(Date theCutoff);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
package ca.uhn.fhir.rest.gclient;
|
||||
|
||||
import ca.uhn.fhir.model.api.Include;
|
||||
import ca.uhn.fhir.rest.api.SearchStyleEnum;
|
||||
import ca.uhn.fhir.rest.api.SortSpec;
|
||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
|
@ -22,14 +30,23 @@ import java.util.Collection;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||
|
||||
import ca.uhn.fhir.model.api.Include;
|
||||
import ca.uhn.fhir.rest.api.SearchStyleEnum;
|
||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||
|
||||
public interface IQuery<Y> extends IBaseQuery<IQuery<Y>>, IClientExecutable<IQuery<Y>, Y> {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
// This is here as an overridden method to allow mocking clients with Mockito to work
|
||||
@Override
|
||||
IQuery<Y> and(ICriterion<?> theCriterion);
|
||||
|
||||
/**
|
||||
* Specifies the <code>_count</code> parameter, which indicates to the server how many resources should be returned
|
||||
* on a single page.
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
IQuery<Y> count(int theCount);
|
||||
|
||||
/**
|
||||
* Add an "_include" specification or an "_include:recurse" specification. If you are using
|
||||
* a constant from one of the built-in structures you can select whether you want recursive
|
||||
|
@ -41,7 +58,12 @@ public interface IQuery<Y> extends IBaseQuery<IQuery<Y>>, IClientExecutable<IQue
|
|||
*/
|
||||
IQuery<Y> include(Include theInclude);
|
||||
|
||||
ISort<Y> sort();
|
||||
/**
|
||||
* Add a "_lastUpdated" specification
|
||||
*
|
||||
* @since HAPI FHIR 1.1 - Note that option was added to FHIR itself in DSTU2
|
||||
*/
|
||||
IQuery<Y> lastUpdated(DateRangeParam theLastUpdated);
|
||||
|
||||
/**
|
||||
* Specifies the <code>_count</code> parameter, which indicates to the server how many resources should be returned
|
||||
|
@ -53,42 +75,31 @@ public interface IQuery<Y> extends IBaseQuery<IQuery<Y>>, IClientExecutable<IQue
|
|||
IQuery<Y> limitTo(int theLimitTo);
|
||||
|
||||
/**
|
||||
* Specifies the <code>_count</code> parameter, which indicates to the server how many resources should be returned
|
||||
* on a single page.
|
||||
* Request that the client return the specified bundle type, e.g. <code>org.hl7.fhir.instance.model.Bundle.class</code>
|
||||
* or <code>ca.uhn.fhir.model.dstu2.resource.Bundle.class</code>
|
||||
*/
|
||||
<B extends IBaseBundle> IQuery<B> returnBundle(Class<B> theClass);
|
||||
|
||||
/**
|
||||
* Add a "_revinclude" specification
|
||||
*
|
||||
* @since 1.4
|
||||
* @since HAPI FHIR 1.0 - Note that option was added to FHIR itself in DSTU2
|
||||
*/
|
||||
IQuery<Y> count(int theCount);
|
||||
IQuery<Y> revInclude(Include theIncludeTarget);
|
||||
|
||||
/**
|
||||
* Match only resources where the resource has the given tag. This parameter corresponds to
|
||||
* the <code>_tag</code> URL parameter.
|
||||
* @param theSystem The tag code system, or <code>null</code> to match any code system (this may not be supported on all servers)
|
||||
* @param theCode The tag code. Must not be <code>null</code> or empty.
|
||||
* Adds a sort criteria
|
||||
*
|
||||
* @see #sort(SortSpec) for an alternate way of speciyfing sorts
|
||||
*/
|
||||
IQuery<Y> withTag(String theSystem, String theCode);
|
||||
ISort<Y> sort();
|
||||
|
||||
/**
|
||||
* Match only resources where the resource has the given security tag. This parameter corresponds to
|
||||
* the <code>_security</code> URL parameter.
|
||||
* @param theSystem The tag code system, or <code>null</code> to match any code system (this may not be supported on all servers)
|
||||
* @param theCode The tag code. Must not be <code>null</code> or empty.
|
||||
* Adds a sort using a {@link SortSpec} object
|
||||
*
|
||||
* @see #sort() for an alternate way of speciyfing sorts
|
||||
*/
|
||||
IQuery<Y> withSecurity(String theSystem, String theCode);
|
||||
|
||||
/**
|
||||
* Match only resources where the resource has the given profile declaration. This parameter corresponds to
|
||||
* the <code>_profile</code> URL parameter.
|
||||
* @param theProfileUri The URI of a given profile to search for resources which match
|
||||
*/
|
||||
IQuery<Y> withProfile(String theProfileUri);
|
||||
|
||||
/**
|
||||
* Matches any of the profiles given as argument. This would result in an OR search for resources matching one or more profiles.
|
||||
* To do an AND search, make multiple calls to {@link #withProfile(String)}.
|
||||
* @param theProfileUris The URIs of a given profile to search for resources which match.
|
||||
*/
|
||||
IQuery<Y> withAnyProfile(Collection<String> theProfileUris);
|
||||
IQuery<Y> sort(SortSpec theSortSpec);
|
||||
|
||||
/**
|
||||
* Forces the query to perform the search using the given method (allowable methods are described in the
|
||||
|
@ -102,28 +113,6 @@ public interface IQuery<Y> extends IBaseQuery<IQuery<Y>>, IClientExecutable<IQue
|
|||
*/
|
||||
IQuery<Y> usingStyle(SearchStyleEnum theStyle);
|
||||
|
||||
IQuery<Y> withIdAndCompartment(String theResourceId, String theCompartmentName);
|
||||
|
||||
/**
|
||||
* Add a "_revinclude" specification
|
||||
*
|
||||
* @since HAPI FHIR 1.0 - Note that option was added to FHIR itself in DSTU2
|
||||
*/
|
||||
IQuery<Y> revInclude(Include theIncludeTarget);
|
||||
|
||||
/**
|
||||
* Add a "_lastUpdated" specification
|
||||
*
|
||||
* @since HAPI FHIR 1.1 - Note that option was added to FHIR itself in DSTU2
|
||||
*/
|
||||
IQuery<Y> lastUpdated(DateRangeParam theLastUpdated);
|
||||
|
||||
/**
|
||||
* Request that the client return the specified bundle type, e.g. <code>org.hl7.fhir.instance.model.Bundle.class</code>
|
||||
* or <code>ca.uhn.fhir.model.dstu2.resource.Bundle.class</code>
|
||||
*/
|
||||
<B extends IBaseBundle> IQuery<B> returnBundle(Class<B> theClass);
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
@ -132,11 +121,40 @@ public interface IQuery<Y> extends IBaseQuery<IQuery<Y>>, IClientExecutable<IQue
|
|||
IQuery<Y> where(ICriterion<?> theCriterion);
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* Matches any of the profiles given as argument. This would result in an OR search for resources matching one or more profiles.
|
||||
* To do an AND search, make multiple calls to {@link #withProfile(String)}.
|
||||
*
|
||||
* @param theProfileUris The URIs of a given profile to search for resources which match.
|
||||
*/
|
||||
// This is here as an overridden method to allow mocking clients with Mockito to work
|
||||
@Override
|
||||
IQuery<Y> and(ICriterion<?> theCriterion);
|
||||
IQuery<Y> withAnyProfile(Collection<String> theProfileUris);
|
||||
|
||||
IQuery<Y> withIdAndCompartment(String theResourceId, String theCompartmentName);
|
||||
|
||||
/**
|
||||
* Match only resources where the resource has the given profile declaration. This parameter corresponds to
|
||||
* the <code>_profile</code> URL parameter.
|
||||
*
|
||||
* @param theProfileUri The URI of a given profile to search for resources which match
|
||||
*/
|
||||
IQuery<Y> withProfile(String theProfileUri);
|
||||
|
||||
/**
|
||||
* Match only resources where the resource has the given security tag. This parameter corresponds to
|
||||
* the <code>_security</code> URL parameter.
|
||||
*
|
||||
* @param theSystem The tag code system, or <code>null</code> to match any code system (this may not be supported on all servers)
|
||||
* @param theCode The tag code. Must not be <code>null</code> or empty.
|
||||
*/
|
||||
IQuery<Y> withSecurity(String theSystem, String theCode);
|
||||
|
||||
/**
|
||||
* Match only resources where the resource has the given tag. This parameter corresponds to
|
||||
* the <code>_tag</code> URL parameter.
|
||||
*
|
||||
* @param theSystem The tag code system, or <code>null</code> to match any code system (this may not be supported on all servers)
|
||||
* @param theCode The tag code. Must not be <code>null</code> or empty.
|
||||
*/
|
||||
IQuery<Y> withTag(String theSystem, String theCode);
|
||||
|
||||
// Y execute();
|
||||
|
||||
|
|
|
@ -42,11 +42,19 @@ public interface ISort<T> {
|
|||
*/
|
||||
IQuery<T> defaultOrder(IParam theParam);
|
||||
|
||||
/**
|
||||
* Sort by the default order. Note that as of STU3, there is no longer
|
||||
* a concept of default order, only ascending and descending. This method
|
||||
* technically implies "ascending" but it makes more sense to use
|
||||
* {@link #ascending(IParam)}
|
||||
*/
|
||||
IQuery<T> defaultOrder(String theParam);
|
||||
|
||||
/**
|
||||
* Sort descending
|
||||
*
|
||||
* @param theParam A query param - Could be a constant such as <code>Patient.ADDRESS</code> or a custom
|
||||
* param such as <code>new StringClientParam("foo")</code>
|
||||
* param such as <code>new StringClientParam("foo")</code>
|
||||
*/
|
||||
IQuery<T> descending(IParam theParam);
|
||||
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
package ca.uhn.fhir.rest.param;
|
||||
|
||||
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.EQUAL;
|
||||
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.GREATERTHAN_OR_EQUALS;
|
||||
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.LESSTHAN_OR_EQUALS;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterAnd;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.rest.api.QualifiedParamList;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.*;
|
||||
import static java.lang.String.format;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
|
@ -25,15 +35,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
import java.util.*;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterAnd;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.rest.api.QualifiedParamList;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
||||
public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
||||
|
||||
|
@ -53,14 +54,12 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
/**
|
||||
* Constructor which takes two Dates representing the lower and upper bounds of the range (inclusive on both ends)
|
||||
*
|
||||
* @param theLowerBound
|
||||
* A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound
|
||||
* A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
*/
|
||||
public DateRangeParam(Date theLowerBound, Date theUpperBound) {
|
||||
this();
|
||||
|
@ -84,22 +83,22 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
setRangeFromDatesInclusive(theDateParam.getValueAsString(), theDateParam.getValueAsString());
|
||||
} else {
|
||||
switch (theDateParam.getPrefix()) {
|
||||
case EQUAL:
|
||||
setRangeFromDatesInclusive(theDateParam.getValueAsString(), theDateParam.getValueAsString());
|
||||
break;
|
||||
case STARTS_AFTER:
|
||||
case GREATERTHAN:
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
validateAndSet(theDateParam, null);
|
||||
break;
|
||||
case ENDS_BEFORE:
|
||||
case LESSTHAN:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
validateAndSet(null, theDateParam);
|
||||
break;
|
||||
default:
|
||||
// Should not happen
|
||||
throw new InvalidRequestException("Invalid comparator for date range parameter:" + theDateParam.getPrefix() + ". This is a bug.");
|
||||
case EQUAL:
|
||||
setRangeFromDatesInclusive(theDateParam.getValueAsString(), theDateParam.getValueAsString());
|
||||
break;
|
||||
case STARTS_AFTER:
|
||||
case GREATERTHAN:
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
validateAndSet(theDateParam, null);
|
||||
break;
|
||||
case ENDS_BEFORE:
|
||||
case LESSTHAN:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
validateAndSet(null, theDateParam);
|
||||
break;
|
||||
default:
|
||||
// Should not happen
|
||||
throw new InvalidRequestException("Invalid comparator for date range parameter:" + theDateParam.getPrefix() + ". This is a bug.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -107,14 +106,12 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
/**
|
||||
* Constructor which takes two Dates representing the lower and upper bounds of the range (inclusive on both ends)
|
||||
*
|
||||
* @param theLowerBound
|
||||
* A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound
|
||||
* A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
*/
|
||||
public DateRangeParam(DateParam theLowerBound, DateParam theUpperBound) {
|
||||
this();
|
||||
|
@ -124,14 +121,12 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
/**
|
||||
* Constructor which takes two Dates representing the lower and upper bounds of the range (inclusive on both ends)
|
||||
*
|
||||
* @param theLowerBound
|
||||
* A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound
|
||||
* A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
*/
|
||||
public DateRangeParam(IPrimitiveType<Date> theLowerBound, IPrimitiveType<Date> theUpperBound) {
|
||||
this();
|
||||
|
@ -141,14 +136,12 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
/**
|
||||
* Constructor which takes two strings representing the lower and upper bounds of the range (inclusive on both ends)
|
||||
*
|
||||
* @param theLowerBound
|
||||
* An unqualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Either theLowerBound or theUpperBound may both be populated, or
|
||||
* one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound
|
||||
* An unqualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Either theLowerBound or theUpperBound may both be populated, or
|
||||
* one may be null, but it is not valid for both to be null.
|
||||
* @param theLowerBound An unqualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Either theLowerBound or theUpperBound may both be populated, or
|
||||
* one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound An unqualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Either theLowerBound or theUpperBound may both be populated, or
|
||||
* one may be null, but it is not valid for both to be null.
|
||||
*/
|
||||
public DateRangeParam(String theLowerBound, String theUpperBound) {
|
||||
this();
|
||||
|
@ -172,31 +165,49 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
} else {
|
||||
|
||||
switch (theParsed.getPrefix()) {
|
||||
case GREATERTHAN:
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
if (myLowerBound != null) {
|
||||
throw new InvalidRequestException("Can not have multiple date range parameters for the same param that specify a lower bound");
|
||||
}
|
||||
myLowerBound = theParsed;
|
||||
break;
|
||||
case LESSTHAN:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
if (myUpperBound != null) {
|
||||
throw new InvalidRequestException("Can not have multiple date range parameters for the same param that specify an upper bound");
|
||||
}
|
||||
myUpperBound = theParsed;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidRequestException("Unknown comparator: " + theParsed.getPrefix());
|
||||
case GREATERTHAN:
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
if (myLowerBound != null) {
|
||||
throw new InvalidRequestException("Can not have multiple date range parameters for the same param that specify a lower bound");
|
||||
}
|
||||
myLowerBound = theParsed;
|
||||
break;
|
||||
case LESSTHAN:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
if (myUpperBound != null) {
|
||||
throw new InvalidRequestException("Can not have multiple date range parameters for the same param that specify an upper bound");
|
||||
}
|
||||
myUpperBound = theParsed;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidRequestException("Unknown comparator: " + theParsed.getPrefix());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof DateRangeParam)) {
|
||||
return false;
|
||||
}
|
||||
DateRangeParam other = (DateRangeParam) obj;
|
||||
return Objects.equals(myLowerBound, other.myLowerBound) &&
|
||||
Objects.equals(myUpperBound, other.myUpperBound);
|
||||
}
|
||||
|
||||
public DateParam getLowerBound() {
|
||||
return myLowerBound;
|
||||
}
|
||||
|
||||
public DateRangeParam setLowerBound(DateParam theLowerBound) {
|
||||
validateAndSet(theLowerBound, myUpperBound);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getLowerBoundAsInstant() {
|
||||
if (myLowerBound == null) {
|
||||
return null;
|
||||
|
@ -204,19 +215,19 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
Date retVal = myLowerBound.getValue();
|
||||
if (myLowerBound.getPrefix() != null) {
|
||||
switch (myLowerBound.getPrefix()) {
|
||||
case GREATERTHAN:
|
||||
case STARTS_AFTER:
|
||||
retVal = myLowerBound.getPrecision().add(retVal, 1);
|
||||
break;
|
||||
case EQUAL:
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
break;
|
||||
case LESSTHAN:
|
||||
case APPROXIMATE:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
case ENDS_BEFORE:
|
||||
case NOT_EQUAL:
|
||||
throw new IllegalStateException("Unvalid lower bound comparator: " + myLowerBound.getPrefix());
|
||||
case GREATERTHAN:
|
||||
case STARTS_AFTER:
|
||||
retVal = myLowerBound.getPrecision().add(retVal, 1);
|
||||
break;
|
||||
case EQUAL:
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
break;
|
||||
case LESSTHAN:
|
||||
case APPROXIMATE:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
case ENDS_BEFORE:
|
||||
case NOT_EQUAL:
|
||||
throw new IllegalStateException("Unvalid lower bound comparator: " + myLowerBound.getPrefix());
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
|
@ -226,6 +237,11 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
return myUpperBound;
|
||||
}
|
||||
|
||||
public DateRangeParam setUpperBound(DateParam theUpperBound) {
|
||||
validateAndSet(myLowerBound, theUpperBound);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getUpperBoundAsInstant() {
|
||||
if (myUpperBound == null) {
|
||||
return null;
|
||||
|
@ -233,21 +249,21 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
Date retVal = myUpperBound.getValue();
|
||||
if (myUpperBound.getPrefix() != null) {
|
||||
switch (myUpperBound.getPrefix()) {
|
||||
case LESSTHAN:
|
||||
case ENDS_BEFORE:
|
||||
retVal = new Date(retVal.getTime() - 1L);
|
||||
break;
|
||||
case EQUAL:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
retVal = myUpperBound.getPrecision().add(retVal, 1);
|
||||
retVal = new Date(retVal.getTime() - 1L);
|
||||
break;
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
case GREATERTHAN:
|
||||
case APPROXIMATE:
|
||||
case NOT_EQUAL:
|
||||
case STARTS_AFTER:
|
||||
throw new IllegalStateException("Unvalid upper bound comparator: " + myUpperBound.getPrefix());
|
||||
case LESSTHAN:
|
||||
case ENDS_BEFORE:
|
||||
retVal = new Date(retVal.getTime() - 1L);
|
||||
break;
|
||||
case EQUAL:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
retVal = myUpperBound.getPrecision().add(retVal, 1);
|
||||
retVal = new Date(retVal.getTime() - 1L);
|
||||
break;
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
case GREATERTHAN:
|
||||
case APPROXIMATE:
|
||||
case NOT_EQUAL:
|
||||
case STARTS_AFTER:
|
||||
throw new IllegalStateException("Unvalid upper bound comparator: " + myUpperBound.getPrefix());
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
|
@ -273,46 +289,55 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
return bound != null && !bound.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(myLowerBound, myUpperBound);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return (getLowerBoundAsInstant() == null) && (getUpperBoundAsInstant() == null);
|
||||
}
|
||||
|
||||
public DateRangeParam setLowerBound(DateParam theLowerBound) {
|
||||
validateAndSet(theLowerBound, myUpperBound);
|
||||
/**
|
||||
* Sets the lower bound using a string that is compliant with
|
||||
* FHIR dateTime format (ISO-8601).
|
||||
* <p>
|
||||
* This lower bound is assumed to have a <code>ge</code>
|
||||
* (greater than or equals) modifier.
|
||||
* </p>
|
||||
*/
|
||||
public DateRangeParam setLowerBound(String theLowerBound) {
|
||||
setLowerBound(new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the range from a pair of dates, inclusive on both ends
|
||||
*
|
||||
* @param theLowerBound
|
||||
* A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound
|
||||
* A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
*/
|
||||
public void setRangeFromDatesInclusive(Date theLowerBound, Date theUpperBound) {
|
||||
DateParam lowerBound = theLowerBound != null
|
||||
? new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound) : null;
|
||||
? new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound) : null;
|
||||
DateParam upperBound = theUpperBound != null
|
||||
? new DateParam(LESSTHAN_OR_EQUALS, theUpperBound) : null;
|
||||
? new DateParam(LESSTHAN_OR_EQUALS, theUpperBound) : null;
|
||||
validateAndSet(lowerBound, upperBound);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the range from a pair of dates, inclusive on both ends
|
||||
*
|
||||
* @param theLowerBound
|
||||
* A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound
|
||||
* A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
*/
|
||||
public void setRangeFromDatesInclusive(DateParam theLowerBound, DateParam theUpperBound) {
|
||||
validateAndSet(theLowerBound, theUpperBound);
|
||||
|
@ -323,14 +348,12 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
* theLowerBound is after theUpperBound, thie method will automatically reverse
|
||||
* the order of the arguments in order to create an inclusive range.
|
||||
*
|
||||
* @param theLowerBound
|
||||
* A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound
|
||||
* A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
*/
|
||||
public void setRangeFromDatesInclusive(IPrimitiveType<Date> theLowerBound, IPrimitiveType<Date> theUpperBound) {
|
||||
IPrimitiveType<Date> lowerBound = theLowerBound;
|
||||
|
@ -350,22 +373,20 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
/**
|
||||
* Sets the range from a pair of dates, inclusive on both ends
|
||||
*
|
||||
* @param theLowerBound
|
||||
* A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound
|
||||
* A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
*/
|
||||
public void setRangeFromDatesInclusive(String theLowerBound, String theUpperBound) {
|
||||
DateParam lowerBound = theLowerBound != null
|
||||
? new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound)
|
||||
: null;
|
||||
? new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound)
|
||||
: null;
|
||||
DateParam upperBound = theUpperBound != null
|
||||
? new DateParam(LESSTHAN_OR_EQUALS, theUpperBound)
|
||||
: null;
|
||||
? new DateParam(LESSTHAN_OR_EQUALS, theUpperBound)
|
||||
: null;
|
||||
if (isNotBlank(theLowerBound) && isNotBlank(theUpperBound) && theLowerBound.equals(theUpperBound)) {
|
||||
lowerBound.setPrefix(EQUAL);
|
||||
upperBound.setPrefix(EQUAL);
|
||||
|
@ -373,14 +394,22 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
validateAndSet(lowerBound, upperBound);
|
||||
}
|
||||
|
||||
public DateRangeParam setUpperBound(DateParam theUpperBound) {
|
||||
validateAndSet(myLowerBound, theUpperBound);
|
||||
/**
|
||||
* Sets the upper bound using a string that is compliant with
|
||||
* FHIR dateTime format (ISO-8601).
|
||||
* <p>
|
||||
* This upper bound is assumed to have a <code>le</code>
|
||||
* (less than or equals) modifier.
|
||||
* </p>
|
||||
*/
|
||||
public DateRangeParam setUpperBound(String theUpperBound) {
|
||||
setUpperBound(new DateParam(LESSTHAN_OR_EQUALS, theUpperBound));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, List<QualifiedParamList> theParameters)
|
||||
throws InvalidRequestException {
|
||||
throws InvalidRequestException {
|
||||
|
||||
boolean haveHadUnqualifiedParameter = false;
|
||||
for (QualifiedParamList paramList : theParameters) {
|
||||
|
@ -413,24 +442,6 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof DateRangeParam)) {
|
||||
return false;
|
||||
}
|
||||
DateRangeParam other = (DateRangeParam) obj;
|
||||
return Objects.equals(myLowerBound, other.myLowerBound) &&
|
||||
Objects.equals(myUpperBound, other.myUpperBound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(myLowerBound, myUpperBound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder b = new StringBuilder();
|
||||
|
@ -463,8 +474,8 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
if (hasBound(lowerBound) && hasBound(upperBound)) {
|
||||
if (lowerBound.getValue().getTime() > upperBound.getValue().getTime()) {
|
||||
throw new DataFormatException(format(
|
||||
"Lower bound of %s is after upper bound of %s",
|
||||
lowerBound.getValueAsString(), upperBound.getValueAsString()));
|
||||
"Lower bound of %s is after upper bound of %s",
|
||||
lowerBound.getValueAsString(), upperBound.getValueAsString()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -473,13 +484,13 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
lowerBound.setPrefix(GREATERTHAN_OR_EQUALS);
|
||||
}
|
||||
switch (lowerBound.getPrefix()) {
|
||||
case GREATERTHAN:
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
default:
|
||||
break;
|
||||
case LESSTHAN:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
throw new DataFormatException("Lower bound comparator must be > or >=, can not be " + lowerBound.getPrefix().getValue());
|
||||
case GREATERTHAN:
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
default:
|
||||
break;
|
||||
case LESSTHAN:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
throw new DataFormatException("Lower bound comparator must be > or >=, can not be " + lowerBound.getPrefix().getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -488,13 +499,13 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
upperBound.setPrefix(LESSTHAN_OR_EQUALS);
|
||||
}
|
||||
switch (upperBound.getPrefix()) {
|
||||
case LESSTHAN:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
default:
|
||||
break;
|
||||
case GREATERTHAN:
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
throw new DataFormatException("Upper bound comparator must be < or <=, can not be " + upperBound.getPrefix().getValue());
|
||||
case LESSTHAN:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
default:
|
||||
break;
|
||||
case GREATERTHAN:
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
throw new DataFormatException("Upper bound comparator must be < or <=, can not be " + upperBound.getPrefix().getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,12 +20,11 @@ package ca.uhn.fhir.rest.server.exceptions;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.util.CoverageIgnore;
|
||||
import ca.uhn.fhir.util.OperationOutcomeUtil;
|
||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||
|
||||
/**
|
||||
* Represents an <b>HTTP 422 Unprocessable Entity</b> response, which means that a resource was rejected by the server because it "violated applicable FHIR profiles or server business rules".
|
||||
|
@ -39,16 +38,15 @@ import ca.uhn.fhir.util.OperationOutcomeUtil;
|
|||
@CoverageIgnore
|
||||
public class UnprocessableEntityException extends BaseServerResponseException {
|
||||
|
||||
public static final int STATUS_CODE = Constants.STATUS_HTTP_422_UNPROCESSABLE_ENTITY;
|
||||
private static final String DEFAULT_MESSAGE = "Unprocessable Entity";
|
||||
private static final long serialVersionUID = 1L;
|
||||
public static final int STATUS_CODE = Constants.STATUS_HTTP_422_UNPROCESSABLE_ENTITY;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param theMessage
|
||||
* The message to add to the status line
|
||||
* @param theOperationOutcome The {@link IBaseOperationOutcome} resource to return to the client
|
||||
* @param theMessage The message to add to the status line
|
||||
* @param theOperationOutcome The {@link IBaseOperationOutcome} resource to return to the client
|
||||
*/
|
||||
public UnprocessableEntityException(String theMessage, IBaseOperationOutcome theOperationOutcome) {
|
||||
super(STATUS_CODE, theMessage, theOperationOutcome);
|
||||
|
@ -79,6 +77,13 @@ public class UnprocessableEntityException extends BaseServerResponseException {
|
|||
super(STATUS_CODE, theMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor which accepts a String describing the issue. This string will be translated into an {@link IBaseOperationOutcome} resource which will be supplied in the response.
|
||||
*/
|
||||
public UnprocessableEntityException(String theMessage, Throwable theCause) {
|
||||
super(STATUS_CODE, theMessage, theCause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor which accepts an array of Strings describing the issue. This strings will be translated into an {@link IBaseOperationOutcome} resource which will be supplied in the response.
|
||||
*/
|
||||
|
|
|
@ -86,7 +86,7 @@ public class ReflectionUtil {
|
|||
public static Class<?> getGenericCollectionTypeOfMethodParameter(Method theMethod, int theParamIndex) {
|
||||
Class<?> type;
|
||||
Type genericParameterType = theMethod.getGenericParameterTypes()[theParamIndex];
|
||||
if (Class.class.equals(genericParameterType)) {
|
||||
if (Class.class.equals(genericParameterType) || Class.class.equals(genericParameterType.getClass())) {
|
||||
return null;
|
||||
}
|
||||
ParameterizedType collectionType = (ParameterizedType) genericParameterType;
|
||||
|
|
|
@ -57,6 +57,7 @@ ca.uhn.fhir.validation.ValidationResult.noIssuesDetected=No issues detected duri
|
|||
|
||||
# JPA Messages
|
||||
|
||||
|
||||
ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect.resourceVersionConstraintFailure=The operation has failed with a version constraint failure. This generally means that two clients/threads were trying to update the same resource at the same time, and this request was chosen as the failing request.
|
||||
ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect.resourceIndexedCompositeStringUniqueConstraintFailure=The operation has failed with a unique index constraint failure. This probably means that the operation was trying to create/update a resource that would have resulted in a duplicate value for a unique index.
|
||||
|
||||
|
@ -90,9 +91,13 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulUpdate=Successfully update
|
|||
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulDeletes=Successfully deleted {0} resource(s) in {1}ms
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidSearchParameter=Unknown search parameter "{0}". Value search parameters for this search are: {1}
|
||||
|
||||
ca.uhn.fhir.jpa.dao.BaseSearchParamExtractor.failedToExtractPaths=Failed to extract values from resource using FHIRPath "{0}": {1}
|
||||
|
||||
ca.uhn.fhir.jpa.dao.SearchBuilder.invalidQuantityPrefix=Unable to handle quantity prefix "{0}" for value: {1}
|
||||
ca.uhn.fhir.jpa.dao.SearchBuilder.invalidNumberPrefix=Unable to handle number prefix "{0}" for value: {1}
|
||||
|
||||
ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4.invalidSearchParamExpression=The expression "{0}" can not be evaluated and may be invalid: {1}
|
||||
|
||||
ca.uhn.fhir.jpa.provider.BaseJpaProvider.cantCombintAtAndSince=Unable to combine _at and _since parameters for history operation
|
||||
|
||||
ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateConceptMapUrl=Can not create multiple ConceptMap resources with ConceptMap.url "{0}", already have one with resource ID: {1}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ca.uhn.fhir.okhttp.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -74,11 +75,11 @@ public class OkHttpRestfulRequest implements IHttpRequest {
|
|||
|
||||
@Override
|
||||
public Map<String, List<String>> getAllHeaders() {
|
||||
return myRequestBuilder.build().headers().toMultimap();
|
||||
return Collections.unmodifiableMap(myRequestBuilder.build().headers().toMultimap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestBodyFromStream() throws IOException {
|
||||
public String getRequestBodyFromStream() {
|
||||
// returning null to indicate this is not supported, as documented in IHttpRequest's contract
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -22,10 +22,7 @@ package ca.uhn.fhir.rest.client.apache;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import ca.uhn.fhir.util.StopWatch;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
@ -70,14 +67,14 @@ public class ApacheHttpRequest implements IHttpRequest {
|
|||
|
||||
@Override
|
||||
public Map<String, List<String>> getAllHeaders() {
|
||||
Map<String, List<String>> result = new HashMap<String, List<String>>();
|
||||
Map<String, List<String>> result = new HashMap<>();
|
||||
for (Header header : myRequest.getAllHeaders()) {
|
||||
if (!result.containsKey(header.getName())) {
|
||||
result.put(header.getName(), new LinkedList<String>());
|
||||
result.put(header.getName(), new LinkedList<>());
|
||||
}
|
||||
result.get(header.getName()).add(header.getValue());
|
||||
}
|
||||
return result;
|
||||
return Collections.unmodifiableMap(result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -316,7 +316,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
|
||||
private static void addParam(Map<String, List<String>> params, String parameterName, String parameterValue) {
|
||||
if (!params.containsKey(parameterName)) {
|
||||
params.put(parameterName, new ArrayList<String>());
|
||||
params.put(parameterName, new ArrayList<>());
|
||||
}
|
||||
params.get(parameterName).add(parameterValue);
|
||||
}
|
||||
|
@ -516,6 +516,19 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
return (QUERY) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QUERY whereMap(Map<String, List<String>> theRawMap) {
|
||||
if (theRawMap != null) {
|
||||
for (String nextKey : theRawMap.keySet()) {
|
||||
for (String nextValue : theRawMap.get(nextKey)) {
|
||||
addParam(myParams, nextKey, nextValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (QUERY) this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public QUERY where(Map<String, List<IQueryParameterType>> theCriterion) {
|
||||
|
@ -743,6 +756,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
private Class<? extends IBaseBundle> myReturnType;
|
||||
private IPrimitiveType mySince;
|
||||
private Class<? extends IBaseResource> myType;
|
||||
private DateRangeParam myAt;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
|
@ -752,6 +766,12 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IHistoryTyped at(DateRangeParam theDateRangeParam) {
|
||||
myAt = theDateRangeParam;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IHistoryTyped count(Integer theCount) {
|
||||
myCount = theCount;
|
||||
|
@ -774,7 +794,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
id = null;
|
||||
}
|
||||
|
||||
HttpGetClientInvocation invocation = HistoryMethodBinding.createHistoryInvocation(myContext, resourceName, id, mySince, myCount);
|
||||
HttpGetClientInvocation invocation = HistoryMethodBinding.createHistoryInvocation(myContext, resourceName, id, mySince, myCount, myAt);
|
||||
|
||||
IClientResponseHandler handler;
|
||||
handler = new ResourceResponseHandler(myReturnType, getPreferResponseTypes(myType));
|
||||
|
@ -1847,6 +1867,16 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IQuery sort(SortSpec theSortSpec) {
|
||||
SortSpec sortSpec = theSortSpec;
|
||||
while (sortSpec != null) {
|
||||
mySort.add(new SortInternal(sortSpec));
|
||||
sortSpec = sortSpec.getChain();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IQuery usingStyle(SearchStyleEnum theStyle) {
|
||||
mySearchStyle = theStyle;
|
||||
|
@ -2133,6 +2163,18 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
myFor = theFor;
|
||||
}
|
||||
|
||||
public SortInternal(SortSpec theSortSpec) {
|
||||
if (theSortSpec.getOrder() == null) {
|
||||
myParamName = Constants.PARAM_SORT;
|
||||
} else if (theSortSpec.getOrder() == SortOrderEnum.ASC) {
|
||||
myParamName = Constants.PARAM_SORT_ASC;
|
||||
} else if (theSortSpec.getOrder() == SortOrderEnum.DESC) {
|
||||
myParamName = Constants.PARAM_SORT_DESC;
|
||||
}
|
||||
myDirection = theSortSpec.getOrder();
|
||||
myParamValue = theSortSpec.getParamName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IQuery ascending(IParam theParam) {
|
||||
myParamName = Constants.PARAM_SORT_ASC;
|
||||
|
@ -2157,6 +2199,14 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
return myFor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IQuery defaultOrder(String theParam) {
|
||||
myParamName = Constants.PARAM_SORT;
|
||||
myDirection = null;
|
||||
myParamValue = theParam;
|
||||
return myFor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IQuery descending(IParam theParam) {
|
||||
myParamName = Constants.PARAM_SORT_DESC;
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.apache.commons.lang3.StringUtils;
|
|||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.client.api.*;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
/**
|
||||
* HTTP interceptor to be used for adding HTTP basic auth username/password tokens
|
||||
|
@ -42,23 +43,29 @@ public class BasicAuthInterceptor implements IClientInterceptor {
|
|||
|
||||
private String myUsername;
|
||||
private String myPassword;
|
||||
private String myHeaderValue;
|
||||
|
||||
public BasicAuthInterceptor(String theUsername, String thePassword) {
|
||||
super();
|
||||
myUsername = theUsername;
|
||||
myPassword = thePassword;
|
||||
/**
|
||||
* @param theUsername The username
|
||||
* @param thePassword The password
|
||||
*/
|
||||
public BasicAuthInterceptor(String theUsername, String thePassword) {
|
||||
this(StringUtils.defaultString(theUsername) + ":" + StringUtils.defaultString(thePassword));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param theCredentialString A credential string in the format <code>username:password</code>
|
||||
*/
|
||||
public BasicAuthInterceptor(String theCredentialString) {
|
||||
Validate.notBlank(theCredentialString, "theCredentialString must not be null or blank");
|
||||
Validate.isTrue(theCredentialString.contains(":"), "theCredentialString must be in the format 'username:password'");
|
||||
String encoded = Base64.encodeBase64String(theCredentialString.getBytes(Constants.CHARSET_US_ASCII));
|
||||
myHeaderValue = "Basic " + encoded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interceptRequest(IHttpRequest theRequest) {
|
||||
String authorizationUnescaped = StringUtils.defaultString(myUsername) + ":" + StringUtils.defaultString(myPassword);
|
||||
String encoded;
|
||||
try {
|
||||
encoded = Base64.encodeBase64String(authorizationUnescaped.getBytes("ISO-8859-1"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new InternalErrorException("Could not find US-ASCII encoding. This shouldn't happen!");
|
||||
}
|
||||
theRequest.addHeader(Constants.HEADER_AUTHORIZATION, ("Basic " + encoded));
|
||||
theRequest.addHeader(Constants.HEADER_AUTHORIZATION, myHeaderValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -66,6 +73,4 @@ public class BasicAuthInterceptor implements IClientInterceptor {
|
|||
// nothing
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -20,19 +20,20 @@ package ca.uhn.fhir.rest.client.interceptor;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
|
||||
import ca.uhn.fhir.rest.client.api.IClientInterceptor;
|
||||
import ca.uhn.fhir.rest.client.api.IHttpRequest;
|
||||
import ca.uhn.fhir.rest.client.api.IHttpResponse;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Client interceptor which simply captures request and response objects and stores them so that they can be inspected after a client
|
||||
* call has returned
|
||||
*
|
||||
* @see ThreadLocalCapturingInterceptor for an interceptor that uses a ThreadLocal in order to work in multithreaded environments
|
||||
*/
|
||||
public class CapturingInterceptor implements IClientInterceptor {
|
||||
|
||||
|
@ -63,10 +64,16 @@ public class CapturingInterceptor implements IClientInterceptor {
|
|||
@Override
|
||||
public void interceptResponse(IHttpResponse theResponse) {
|
||||
//Buffer the reponse to avoid errors when content has already been read and the entity is not repeatable
|
||||
bufferResponse(theResponse);
|
||||
|
||||
myLastResponse = theResponse;
|
||||
}
|
||||
|
||||
static void bufferResponse(IHttpResponse theResponse) {
|
||||
try {
|
||||
if(theResponse.getResponse() instanceof HttpResponse) {
|
||||
if (theResponse.getResponse() instanceof HttpResponse) {
|
||||
HttpEntity entity = ((HttpResponse) theResponse.getResponse()).getEntity();
|
||||
if( entity != null && !entity.isRepeatable()){
|
||||
if (entity != null && !entity.isRepeatable()) {
|
||||
theResponse.bufferEntity();
|
||||
}
|
||||
} else {
|
||||
|
@ -75,9 +82,6 @@ public class CapturingInterceptor implements IClientInterceptor {
|
|||
} catch (IOException e) {
|
||||
throw new InternalErrorException("Unable to buffer the entity for capturing", e);
|
||||
}
|
||||
|
||||
|
||||
myLastResponse = theResponse;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
package ca.uhn.fhir.rest.client.interceptor;
|
||||
|
||||
import ca.uhn.fhir.rest.client.api.IClientInterceptor;
|
||||
import ca.uhn.fhir.rest.client.api.IHttpRequest;
|
||||
import ca.uhn.fhir.rest.client.api.IHttpResponse;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* This is a client interceptor that captures the current request and response
|
||||
* in a ThreadLocal variable, meaning that it can work in multithreaded
|
||||
* environments without mixing up requests.
|
||||
* <p>
|
||||
* Use this with caution, since <b>this interceptor does not automatically clean up</b>
|
||||
* the ThreadLocal after setting it. You must make sure to call
|
||||
* {@link #clearThreadLocals()} after a given request has been completed,
|
||||
* or you will end up leaving stale request/response objects associated
|
||||
* with threads that no longer need them.
|
||||
* </p>
|
||||
*
|
||||
* @see CapturingInterceptor for an equivalent interceptor that does not use a ThreadLocal
|
||||
* @since 3.5.0
|
||||
*/
|
||||
public class ThreadLocalCapturingInterceptor implements IClientInterceptor {
|
||||
|
||||
private final ThreadLocal<IHttpRequest> myRequestThreadLocal = new ThreadLocal<>();
|
||||
private final ThreadLocal<IHttpResponse> myResponseThreadLocal = new ThreadLocal<>();
|
||||
private boolean myBufferResponse;
|
||||
|
||||
/**
|
||||
* This method should be called at the end of any request process, in
|
||||
* order to clear the last request and response from the current thread.
|
||||
*/
|
||||
public void clearThreadLocals() {
|
||||
myRequestThreadLocal.remove();
|
||||
myResponseThreadLocal.remove();
|
||||
}
|
||||
|
||||
public IHttpRequest getRequestForCurrentThread() {
|
||||
return myRequestThreadLocal.get();
|
||||
}
|
||||
|
||||
public IHttpResponse getResponseForCurrentThread() {
|
||||
return myResponseThreadLocal.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interceptRequest(IHttpRequest theRequest) {
|
||||
myRequestThreadLocal.set(theRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interceptResponse(IHttpResponse theResponse) {
|
||||
if (isBufferResponse()) {
|
||||
CapturingInterceptor.bufferResponse(theResponse);
|
||||
}
|
||||
myResponseThreadLocal.set(theResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we buffer (capture) the response body? This defaults to
|
||||
* <code>false</code>. Set to <code>true</code> if you are planning on
|
||||
* examining response bodies after the response processing is complete.
|
||||
*/
|
||||
public boolean isBufferResponse() {
|
||||
return myBufferResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we buffer (capture) the response body? This defaults to
|
||||
* <code>false</code>. Set to <code>true</code> if you are planning on
|
||||
* examining response bodies after the response processing is complete.
|
||||
*/
|
||||
public ThreadLocalCapturingInterceptor setBufferResponse(boolean theBufferResponse) {
|
||||
myBufferResponse = theBufferResponse;
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -26,6 +26,8 @@ import java.lang.reflect.Method;
|
|||
import java.lang.reflect.Modifier;
|
||||
import java.util.Date;
|
||||
|
||||
import ca.uhn.fhir.rest.param.DateParam;
|
||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
|
@ -96,7 +98,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
}
|
||||
|
||||
String historyId = id != null ? id.getIdPart() : null;
|
||||
HttpGetClientInvocation retVal = createHistoryInvocation(getContext(), resourceName, historyId, null, null);
|
||||
HttpGetClientInvocation retVal = createHistoryInvocation(getContext(), resourceName, historyId, null, null, null);
|
||||
|
||||
if (theArgs != null) {
|
||||
for (int idx = 0; idx < theArgs.length; idx++) {
|
||||
|
@ -108,7 +110,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
public static HttpGetClientInvocation createHistoryInvocation(FhirContext theContext, String theResourceName, String theId, IPrimitiveType<Date> theSince, Integer theLimit) {
|
||||
public static HttpGetClientInvocation createHistoryInvocation(FhirContext theContext, String theResourceName, String theId, IPrimitiveType<Date> theSince, Integer theLimit, DateRangeParam theAt) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
if (theResourceName != null) {
|
||||
b.append(theResourceName);
|
||||
|
@ -129,8 +131,18 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
}
|
||||
if (theLimit != null) {
|
||||
b.append(haveParam ? '&' : '?');
|
||||
haveParam = true;
|
||||
b.append(Constants.PARAM_COUNT).append('=').append(theLimit);
|
||||
}
|
||||
if (theAt != null) {
|
||||
for (DateParam next : theAt.getValuesAsQueryTokens()) {
|
||||
b.append(haveParam ? '&' : '?');
|
||||
haveParam = true;
|
||||
b.append(Constants.PARAM_AT);
|
||||
b.append("=");
|
||||
b.append(next.getValueAsQueryToken(theContext));
|
||||
}
|
||||
}
|
||||
|
||||
HttpGetClientInvocation retVal = new HttpGetClientInvocation(theContext, b.toString());
|
||||
return retVal;
|
||||
|
|
|
@ -28,10 +28,7 @@ import ca.uhn.fhir.util.StopWatch;
|
|||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.client.Invocation;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A Http Request based on JaxRs. This is an adapter around the class
|
||||
|
@ -41,7 +38,7 @@ import java.util.Map;
|
|||
*/
|
||||
public class JaxRsHttpRequest implements IHttpRequest {
|
||||
|
||||
private final Map<String, List<String>> myHeaders = new HashMap<String, List<String>>();
|
||||
private final Map<String, List<String>> myHeaders = new HashMap<>();
|
||||
private Invocation.Builder myRequest;
|
||||
private RequestTypeEnum myRequestType;
|
||||
private Entity<?> myEntity;
|
||||
|
@ -55,7 +52,7 @@ public class JaxRsHttpRequest implements IHttpRequest {
|
|||
@Override
|
||||
public void addHeader(String theName, String theValue) {
|
||||
if (!myHeaders.containsKey(theName)) {
|
||||
myHeaders.put(theName, new LinkedList<String>());
|
||||
myHeaders.put(theName, new LinkedList<>());
|
||||
}
|
||||
myHeaders.get(theName).add(theValue);
|
||||
getRequest().header(theName, theValue);
|
||||
|
@ -71,7 +68,7 @@ public class JaxRsHttpRequest implements IHttpRequest {
|
|||
|
||||
@Override
|
||||
public Map<String, List<String>> getAllHeaders() {
|
||||
return this.myHeaders;
|
||||
return Collections.unmodifiableMap(this.myHeaders);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -108,7 +108,7 @@ public class BaseR4Config extends BaseConfig {
|
|||
@Bean(name = "myResourceCountsCache")
|
||||
public ResourceCountCache resourceCountsCache() {
|
||||
ResourceCountCache retVal = new ResourceCountCache(() -> systemDaoR4().getResourceCounts());
|
||||
retVal.setCacheMillis(60 * DateUtils.MILLIS_PER_SECOND);
|
||||
retVal.setCacheMillis(10 * DateUtils.MILLIS_PER_MINUTE);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
|
|
@ -74,7 +74,6 @@ import javax.persistence.criteria.Root;
|
|||
import javax.xml.stream.events.Characters;
|
||||
import javax.xml.stream.events.XMLEvent;
|
||||
import java.io.CharArrayWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.text.Normalizer;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
@ -1250,25 +1249,10 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
|||
if (theEntity.getDeleted() == null) {
|
||||
|
||||
encoding = myConfig.getResourceEncoding();
|
||||
IParser parser = encoding.newParser(myContext);
|
||||
parser.setDontEncodeElements(EXCLUDE_ELEMENTS_IN_ENCODED);
|
||||
String encoded = parser.encodeResourceToString(theResource);
|
||||
|
||||
Set<String> excludeElements = EXCLUDE_ELEMENTS_IN_ENCODED;
|
||||
theEntity.setFhirVersion(myContext.getVersion().getVersion());
|
||||
switch (encoding) {
|
||||
case JSON:
|
||||
bytes = encoded.getBytes(Charsets.UTF_8);
|
||||
break;
|
||||
case JSONC:
|
||||
bytes = GZipUtil.compress(encoded);
|
||||
break;
|
||||
default:
|
||||
case DEL:
|
||||
bytes = new byte[0];
|
||||
break;
|
||||
}
|
||||
|
||||
ourLog.debug("Encoded {} chars of resource body as {} bytes", encoded.length(), bytes.length);
|
||||
bytes = encodeResource(theResource, encoding, excludeElements, myContext);
|
||||
|
||||
if (theUpdateHash) {
|
||||
HashFunction sha256 = Hashing.sha256();
|
||||
|
@ -1665,21 +1649,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
|||
}
|
||||
|
||||
// 2. get The text
|
||||
String resourceText = null;
|
||||
switch (resourceEncoding) {
|
||||
case JSON:
|
||||
try {
|
||||
resourceText = new String(resourceBytes, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new Error("Should not happen", e);
|
||||
}
|
||||
break;
|
||||
case JSONC:
|
||||
resourceText = GZipUtil.decompress(resourceBytes);
|
||||
break;
|
||||
case DEL:
|
||||
break;
|
||||
}
|
||||
String resourceText = decodeResource(resourceBytes, resourceEncoding);
|
||||
|
||||
// 3. Use the appropriate custom type if one is specified in the context
|
||||
Class<R> resourceType = theResourceType;
|
||||
|
@ -2393,6 +2363,45 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
|||
|
||||
}
|
||||
|
||||
public static String decodeResource(byte[] theResourceBytes, ResourceEncodingEnum theResourceEncoding) {
|
||||
String resourceText = null;
|
||||
switch (theResourceEncoding) {
|
||||
case JSON:
|
||||
resourceText = new String(theResourceBytes, Charsets.UTF_8);
|
||||
break;
|
||||
case JSONC:
|
||||
resourceText = GZipUtil.decompress(theResourceBytes);
|
||||
break;
|
||||
case DEL:
|
||||
break;
|
||||
}
|
||||
return resourceText;
|
||||
}
|
||||
|
||||
public static byte[] encodeResource(IBaseResource theResource, ResourceEncodingEnum theEncoding, Set<String> theExcludeElements, FhirContext theContext) {
|
||||
byte[] bytes;
|
||||
IParser parser = theEncoding.newParser(theContext);
|
||||
parser.setDontEncodeElements(theExcludeElements);
|
||||
String encoded = parser.encodeResourceToString(theResource);
|
||||
|
||||
|
||||
switch (theEncoding) {
|
||||
case JSON:
|
||||
bytes = encoded.getBytes(Charsets.UTF_8);
|
||||
break;
|
||||
case JSONC:
|
||||
bytes = GZipUtil.compress(encoded);
|
||||
break;
|
||||
default:
|
||||
case DEL:
|
||||
bytes = new byte[0];
|
||||
break;
|
||||
}
|
||||
|
||||
ourLog.debug("Encoded {} chars of resource body as {} bytes", encoded.length(), bytes.length);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to create a set of all possible combinations of
|
||||
* parameters across a set of search parameters. An example of why
|
||||
|
|
|
@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.dao;
|
|||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
|
@ -47,6 +48,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
private DaoConfig myDaoConfig;
|
||||
@Autowired
|
||||
private ISearchParamRegistry mySearchParamRegistry;
|
||||
|
||||
public BaseSearchParamExtractor() {
|
||||
super();
|
||||
}
|
||||
|
@ -73,31 +75,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
return refs;
|
||||
}
|
||||
|
||||
protected List<Object> extractValues(String thePaths, IBaseResource theResource) {
|
||||
List<Object> values = new ArrayList<Object>();
|
||||
String[] nextPathsSplit = SPLIT.split(thePaths);
|
||||
FhirTerser t = myContext.newTerser();
|
||||
for (String nextPath : nextPathsSplit) {
|
||||
String nextPathTrimmed = nextPath.trim();
|
||||
try {
|
||||
List<Object> allValues = t.getValues(theResource, nextPathTrimmed);
|
||||
for (Object next : allValues) {
|
||||
if (next instanceof IBaseExtension) {
|
||||
IBaseDatatype value = ((IBaseExtension) next).getValue();
|
||||
if (value != null) {
|
||||
values.add(value);
|
||||
}
|
||||
} else {
|
||||
values.add(next);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
|
||||
ourLog.warn("Failed to index values from path[{}] in resource type[{}]: {}", new Object[] {nextPathTrimmed, def.getName(), e.toString(), e});
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
protected abstract List<Object> extractValues(String thePaths, IBaseResource theResource);
|
||||
|
||||
protected FhirContext getContext() {
|
||||
return myContext;
|
||||
|
|
|
@ -144,6 +144,7 @@ public class DaoConfig {
|
|||
private boolean myExpungeEnabled;
|
||||
private int myReindexThreadCount;
|
||||
private Set<String> myBundleTypesAllowedForStorage;
|
||||
private boolean myValidateSearchParameterExpressionsOnSave = true;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -786,7 +787,6 @@ public class DaoConfig {
|
|||
this.myAllowContainsSearches = theAllowContainsSearches;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If set to <code>true</code> (default is <code>false</code>) the server will allow
|
||||
* resources to have references to external servers. For example if this server is
|
||||
|
@ -1188,6 +1188,34 @@ public class DaoConfig {
|
|||
myUniqueIndexesEnabled = theUniqueIndexesEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* If <code>true</code> (default is <code>true</code>), before allowing a
|
||||
* SearchParameter resource to be stored (create, update, etc.) the
|
||||
* expression will be performed against an empty resource to ensure that
|
||||
* the FHIRPath executor is able to process it.
|
||||
* <p>
|
||||
* This should proabably always be set to true, but is configurable
|
||||
* in order to support some unit tests.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isValidateSearchParameterExpressionsOnSave() {
|
||||
return myValidateSearchParameterExpressionsOnSave;
|
||||
}
|
||||
|
||||
/**
|
||||
* If <code>true</code> (default is <code>true</code>), before allowing a
|
||||
* SearchParameter resource to be stored (create, update, etc.) the
|
||||
* expression will be performed against an empty resource to ensure that
|
||||
* the FHIRPath executor is able to process it.
|
||||
* <p>
|
||||
* This should proabably always be set to true, but is configurable
|
||||
* in order to support some unit tests.
|
||||
* </p>
|
||||
*/
|
||||
public void setValidateSearchParameterExpressionsOnSave(boolean theValidateSearchParameterExpressionsOnSave) {
|
||||
myValidateSearchParameterExpressionsOnSave = theValidateSearchParameterExpressionsOnSave;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not call this method, it exists only for legacy reasons. It
|
||||
* will be removed in a future version. Configure the page size on your
|
||||
|
|
|
@ -29,14 +29,8 @@ import ca.uhn.fhir.model.dstu2.resource.SearchParameter;
|
|||
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SearchParamTypeEnum;
|
||||
import ca.uhn.fhir.model.primitive.BoundCodeDt;
|
||||
import ca.uhn.fhir.model.primitive.CodeDt;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
@ -83,7 +77,7 @@ public class FhirResourceDaoSearchParameterDstu2 extends FhirResourceDaoDstu2<Se
|
|||
FhirContext context = getContext();
|
||||
SearchParamTypeEnum type = theResource.getTypeElement().getValueAsEnum();
|
||||
|
||||
FhirResourceDaoSearchParameterR4.validateSearchParam(type, status, base, expression, context);
|
||||
FhirResourceDaoSearchParameterR4.validateSearchParam(type, status, base, expression, context, getConfig());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -19,33 +19,40 @@ package ca.uhn.fhir.jpa.dao;
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
import javax.measure.quantity.Quantity;
|
||||
import javax.measure.unit.NonSI;
|
||||
import javax.measure.unit.Unit;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.jpa.entity.*;
|
||||
import ca.uhn.fhir.model.api.*;
|
||||
import ca.uhn.fhir.model.api.IDatatype;
|
||||
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
|
||||
import ca.uhn.fhir.model.api.IValueSetEnumBinder;
|
||||
import ca.uhn.fhir.model.base.composite.BaseHumanNameDt;
|
||||
import ca.uhn.fhir.model.dstu2.composite.*;
|
||||
import ca.uhn.fhir.model.dstu2.composite.BoundCodeableConceptDt;
|
||||
import ca.uhn.fhir.model.dstu2.resource.*;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Conformance.RestSecurity;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Location;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Patient.Communication;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Questionnaire;
|
||||
import ca.uhn.fhir.model.dstu2.resource.ValueSet;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.RestfulSecurityServiceEnum;
|
||||
import ca.uhn.fhir.model.primitive.*;
|
||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.hl7.fhir.instance.model.api.IBaseDatatype;
|
||||
import org.hl7.fhir.instance.model.api.IBaseExtension;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import javax.measure.quantity.Quantity;
|
||||
import javax.measure.unit.NonSI;
|
||||
import javax.measure.unit.Unit;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implements ISearchParamExtractor {
|
||||
|
||||
|
@ -87,7 +94,7 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen
|
|||
*/
|
||||
@Override
|
||||
public Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) {
|
||||
HashSet<ResourceIndexedSearchParamDate> retVal = new HashSet<ResourceIndexedSearchParamDate>();
|
||||
HashSet<ResourceIndexedSearchParamDate> retVal = new HashSet<>();
|
||||
|
||||
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
|
||||
for (RuntimeSearchParam nextSpDef : searchParams) {
|
||||
|
@ -626,6 +633,35 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Object> extractValues(String thePaths, IBaseResource theResource) {
|
||||
List<Object> values = new ArrayList<>();
|
||||
String[] nextPathsSplit = SPLIT.split(thePaths);
|
||||
FhirTerser t = getContext().newTerser();
|
||||
for (String nextPath : nextPathsSplit) {
|
||||
String nextPathTrimmed = nextPath.trim();
|
||||
List<Object> allValues;
|
||||
try {
|
||||
allValues = t.getValues(theResource, nextPathTrimmed);
|
||||
} catch (Exception e) {
|
||||
String msg = getContext().getLocalizer().getMessage(BaseSearchParamExtractor.class, "failedToExtractPaths", nextPath, e.toString());
|
||||
throw new InternalErrorException(msg, e);
|
||||
}
|
||||
for (Object next : allValues) {
|
||||
if (next instanceof IBaseExtension) {
|
||||
IBaseDatatype value = ((IBaseExtension) next).getValue();
|
||||
if (value != null) {
|
||||
values.add(value);
|
||||
}
|
||||
} else {
|
||||
values.add(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
private static <T extends Enum<?>> String extractSystem(BoundCodeDt<T> theBoundCode) {
|
||||
if (theBoundCode.getValueAsEnum() != null) {
|
||||
IValueSetEnumBinder<T> binder = theBoundCode.getBinder();
|
||||
|
|
|
@ -74,7 +74,7 @@ public class FhirResourceDaoSearchParameterDstu3 extends FhirResourceDaoDstu3<Se
|
|||
FhirContext context = getContext();
|
||||
Enumerations.SearchParamType type = theResource.getType();
|
||||
|
||||
FhirResourceDaoSearchParameterR4.validateSearchParam(type, status, base, expression, context);
|
||||
FhirResourceDaoSearchParameterR4.validateSearchParam(type, status, base, expression, context, getConfig());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,17 +19,15 @@ package ca.uhn.fhir.jpa.dao.dstu3;
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.trim;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.measure.unit.NonSI;
|
||||
import javax.measure.unit.Unit;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.jpa.dao.*;
|
||||
import ca.uhn.fhir.jpa.entity.*;
|
||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext;
|
||||
|
@ -41,16 +39,18 @@ import org.hl7.fhir.dstu3.model.Location.LocationPositionComponent;
|
|||
import org.hl7.fhir.dstu3.model.Patient.PatientCommunicationComponent;
|
||||
import org.hl7.fhir.dstu3.utils.FHIRPathEngine;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.measure.unit.NonSI;
|
||||
import javax.measure.unit.Unit;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
import ca.uhn.fhir.context.*;
|
||||
import ca.uhn.fhir.jpa.dao.*;
|
||||
import ca.uhn.fhir.jpa.entity.*;
|
||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import static org.apache.commons.lang3.StringUtils.*;
|
||||
|
||||
public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implements ISearchParamExtractor {
|
||||
|
||||
|
@ -61,11 +61,6 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
|
|||
|
||||
private HapiWorkerContext myWorkerContext;
|
||||
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
myWorkerContext = new HapiWorkerContext(getContext(), myValidationSupport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
|
@ -78,6 +73,17 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
|
|||
myValidationSupport = theValidationSupport;
|
||||
}
|
||||
|
||||
private void addQuantity(ResourceTable theEntity, HashSet<ResourceIndexedSearchParamQuantity> retVal, String resourceName, Quantity nextValue) {
|
||||
if (!nextValue.getValueElement().isEmpty()) {
|
||||
BigDecimal nextValueValue = nextValue.getValueElement().getValue();
|
||||
String nextValueString = nextValue.getSystemElement().getValueAsString();
|
||||
String nextValueCode = nextValue.getCode();
|
||||
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(resourceName, nextValueValue, nextValueString, nextValueCode);
|
||||
nextEntity.setResource(theEntity);
|
||||
retVal.add(nextEntity);
|
||||
}
|
||||
}
|
||||
|
||||
private void addSearchTerm(ResourceTable theEntity, Set<ResourceIndexedSearchParamString> retVal, String resourceName, String searchTerm) {
|
||||
if (isBlank(searchTerm)) {
|
||||
return;
|
||||
|
@ -100,6 +106,23 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
|
|||
retVal.add(nextEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PathAndRef> extractResourceLinks(IBaseResource theResource, RuntimeSearchParam theNextSpDef) {
|
||||
ArrayList<PathAndRef> retVal = new ArrayList<PathAndRef>();
|
||||
|
||||
String[] nextPathsSplit = SPLIT.split(theNextSpDef.getPath());
|
||||
for (String path : nextPathsSplit) {
|
||||
path = path.trim();
|
||||
if (isNotBlank(path)) {
|
||||
for (Object next : extractValues(path, theResource)) {
|
||||
retVal.add(new PathAndRef(path, next));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ResourceIndexedSearchParamCoords> extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
|
||||
// TODO: implement
|
||||
|
@ -200,7 +223,7 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
|
|||
*/
|
||||
@Override
|
||||
public HashSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) {
|
||||
HashSet<ResourceIndexedSearchParamNumber> retVal = new HashSet<ResourceIndexedSearchParamNumber>();
|
||||
HashSet<ResourceIndexedSearchParamNumber> retVal = new HashSet<>();
|
||||
|
||||
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
|
||||
for (RuntimeSearchParam nextSpDef : searchParams) {
|
||||
|
@ -349,17 +372,6 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private void addQuantity(ResourceTable theEntity, HashSet<ResourceIndexedSearchParamQuantity> retVal, String resourceName, Quantity nextValue) {
|
||||
if (!nextValue.getValueElement().isEmpty()) {
|
||||
BigDecimal nextValueValue = nextValue.getValueElement().getValue();
|
||||
String nextValueString = nextValue.getSystemElement().getValueAsString();
|
||||
String nextValueCode = nextValue.getCode();
|
||||
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(resourceName, nextValueValue, nextValueString, nextValueCode);
|
||||
nextEntity.setResource(theEntity);
|
||||
retVal.add(nextEntity);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
|
@ -674,14 +686,14 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
|
|||
}
|
||||
|
||||
private void extractTokensFromCodeableConcept(List<String> theSystems, List<String> theCodes, CodeableConcept theCodeableConcept, ResourceTable theEntity,
|
||||
Set<BaseResourceIndexedSearchParam> theListToPopulate, RuntimeSearchParam theParameterDef) {
|
||||
Set<BaseResourceIndexedSearchParam> theListToPopulate, RuntimeSearchParam theParameterDef) {
|
||||
for (Coding nextCoding : theCodeableConcept.getCoding()) {
|
||||
extractTokensFromCoding(theSystems, theCodes, theEntity, theListToPopulate, theParameterDef, nextCoding);
|
||||
}
|
||||
}
|
||||
|
||||
private void extractTokensFromCoding(List<String> theSystems, List<String> theCodes, ResourceTable theEntity, Set<BaseResourceIndexedSearchParam> theListToPopulate,
|
||||
RuntimeSearchParam theParameterDef, Coding nextCoding) {
|
||||
RuntimeSearchParam theParameterDef, Coding nextCoding) {
|
||||
if (nextCoding != null && !nextCoding.isEmpty()) {
|
||||
|
||||
String nextSystem = nextCoding.getSystemElement().getValueAsString();
|
||||
|
@ -706,16 +718,18 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
|
|||
FHIRPathEngine fp = new FHIRPathEngine(myWorkerContext);
|
||||
|
||||
List<Object> values = new ArrayList<>();
|
||||
try {
|
||||
String[] nextPathsSplit = SPLIT.split(thePaths);
|
||||
for (String nextPath : nextPathsSplit) {
|
||||
List<Base> allValues = fp.evaluate((Base) theResource, trim(nextPath));
|
||||
if (allValues.isEmpty() == false) {
|
||||
values.addAll(allValues);
|
||||
}
|
||||
String[] nextPathsSplit = SPLIT.split(thePaths);
|
||||
for (String nextPath : nextPathsSplit) {
|
||||
List<Base> allValues;
|
||||
try {
|
||||
allValues = fp.evaluate((Base) theResource, trim(nextPath));
|
||||
} catch (FHIRException e) {
|
||||
String msg = getContext().getLocalizer().getMessage(BaseSearchParamExtractor.class, "failedToExtractPaths", nextPath, e.toString());
|
||||
throw new InternalErrorException(msg, e);
|
||||
}
|
||||
if (allValues.isEmpty() == false) {
|
||||
values.addAll(allValues);
|
||||
}
|
||||
} catch (FHIRException e) {
|
||||
throw new InternalErrorException(e);
|
||||
}
|
||||
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
|
@ -730,28 +744,16 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
|
|||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PathAndRef> extractResourceLinks(IBaseResource theResource, RuntimeSearchParam theNextSpDef) {
|
||||
ArrayList<PathAndRef> retVal = new ArrayList<PathAndRef>();
|
||||
|
||||
String[] nextPathsSplit = SPLIT.split(theNextSpDef.getPath());
|
||||
for (String path : nextPathsSplit) {
|
||||
path = path.trim();
|
||||
if (isNotBlank(path)) {
|
||||
for (Object next : extractValues(path, theResource)) {
|
||||
retVal.add(new PathAndRef(path, next));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setValidationSupportForTesting(org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport theValidationSupport) {
|
||||
myValidationSupport = theValidationSupport;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
myWorkerContext = new HapiWorkerContext(getContext(), myValidationSupport);
|
||||
}
|
||||
|
||||
private static <T extends Enum<?>> String extractSystem(Enumeration<T> theBoundCode) {
|
||||
if (theBoundCode.getValue() != null) {
|
||||
return theBoundCode.getEnumFactory().toSystem(theBoundCode.getValue());
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
package ca.uhn.fhir.jpa.dao.r4;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.jpa.dao.BaseSearchParamExtractor;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSearchParameter;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.util.ElementUtil;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -76,10 +80,10 @@ public class FhirResourceDaoSearchParameterR4 extends FhirResourceDaoR4<SearchPa
|
|||
FhirContext context = getContext();
|
||||
Enum<?> type = theResource.getType();
|
||||
|
||||
FhirResourceDaoSearchParameterR4.validateSearchParam(type, status, base, expression, context);
|
||||
FhirResourceDaoSearchParameterR4.validateSearchParam(type, status, base, expression, context, getConfig());
|
||||
}
|
||||
|
||||
public static void validateSearchParam(Enum<?> theType, Enum<?> theStatus, List<? extends IPrimitiveType> theBase, String theExpression, FhirContext theContext) {
|
||||
public static void validateSearchParam(Enum<?> theType, Enum<?> theStatus, List<? extends IPrimitiveType> theBase, String theExpression, FhirContext theContext, DaoConfig theDaoConfig) {
|
||||
if (theStatus == null) {
|
||||
throw new UnprocessableEntityException("SearchParameter.status is missing or invalid");
|
||||
}
|
||||
|
@ -116,6 +120,17 @@ public class FhirResourceDaoSearchParameterR4 extends FhirResourceDaoR4<SearchPa
|
|||
throw new UnprocessableEntityException("Invalid SearchParameter.expression value \"" + nextPath + "\": " + e.getMessage());
|
||||
}
|
||||
|
||||
if (theContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
|
||||
if (theDaoConfig.isValidateSearchParameterExpressionsOnSave()) {
|
||||
IBaseResource temporaryInstance = theContext.getResourceDefinition(resourceName).newInstance();
|
||||
try {
|
||||
theContext.newFluentPath().evaluate(temporaryInstance, nextPath, IBase.class);
|
||||
} catch (Exception e) {
|
||||
String msg = theContext.getLocalizer().getMessage(FhirResourceDaoSearchParameterR4.class, "invalidSearchParamExpression", nextPath, e.getMessage());
|
||||
throw new UnprocessableEntityException(msg, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // if have expression
|
||||
|
|
|
@ -19,17 +19,21 @@ package ca.uhn.fhir.jpa.dao.r4;
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
import javax.measure.unit.NonSI;
|
||||
import javax.measure.unit.Unit;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.jpa.dao.*;
|
||||
import ca.uhn.fhir.jpa.entity.*;
|
||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.hl7.fhir.r4.context.IWorkerContext;
|
||||
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
|
@ -38,17 +42,15 @@ import org.hl7.fhir.r4.model.Enumeration;
|
|||
import org.hl7.fhir.r4.model.Location.LocationPositionComponent;
|
||||
import org.hl7.fhir.r4.model.Patient.PatientCommunicationComponent;
|
||||
import org.hl7.fhir.r4.utils.FHIRPathEngine;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import javax.measure.unit.NonSI;
|
||||
import javax.measure.unit.Unit;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
import ca.uhn.fhir.context.*;
|
||||
import ca.uhn.fhir.jpa.dao.*;
|
||||
import ca.uhn.fhir.jpa.entity.*;
|
||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements ISearchParamExtractor {
|
||||
|
||||
|
@ -69,6 +71,17 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
|
|||
myValidationSupport = theValidationSupport;
|
||||
}
|
||||
|
||||
private void addQuantity(ResourceTable theEntity, HashSet<ResourceIndexedSearchParamQuantity> retVal, String resourceName, Quantity nextValue) {
|
||||
if (!nextValue.getValueElement().isEmpty()) {
|
||||
BigDecimal nextValueValue = nextValue.getValueElement().getValue();
|
||||
String nextValueString = nextValue.getSystemElement().getValueAsString();
|
||||
String nextValueCode = nextValue.getCode();
|
||||
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(resourceName, nextValueValue, nextValueString, nextValueCode);
|
||||
nextEntity.setResource(theEntity);
|
||||
retVal.add(nextEntity);
|
||||
}
|
||||
}
|
||||
|
||||
private void addSearchTerm(ResourceTable theEntity, Set<ResourceIndexedSearchParamString> retVal, String resourceName, String searchTerm) {
|
||||
if (isBlank(searchTerm)) {
|
||||
return;
|
||||
|
@ -91,6 +104,23 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
|
|||
retVal.add(nextEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PathAndRef> extractResourceLinks(IBaseResource theResource, RuntimeSearchParam theNextSpDef) {
|
||||
ArrayList<PathAndRef> retVal = new ArrayList<>();
|
||||
|
||||
String[] nextPathsSplit = SPLIT.split(theNextSpDef.getPath());
|
||||
for (String path : nextPathsSplit) {
|
||||
path = path.trim();
|
||||
if (isNotBlank(path)) {
|
||||
for (Object next : extractValues(path, theResource)) {
|
||||
retVal.add(new PathAndRef(path, next));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ResourceIndexedSearchParamCoords> extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
|
||||
// TODO: implement
|
||||
|
@ -336,17 +366,6 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private void addQuantity(ResourceTable theEntity, HashSet<ResourceIndexedSearchParamQuantity> retVal, String resourceName, Quantity nextValue) {
|
||||
if (!nextValue.getValueElement().isEmpty()) {
|
||||
BigDecimal nextValueValue = nextValue.getValueElement().getValue();
|
||||
String nextValueString = nextValue.getSystemElement().getValueAsString();
|
||||
String nextValueCode = nextValue.getCode();
|
||||
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(resourceName, nextValueValue, nextValueString, nextValueCode);
|
||||
nextEntity.setResource(theEntity);
|
||||
retVal.add(nextEntity);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
|
@ -658,14 +677,14 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
|
|||
}
|
||||
|
||||
private void extractTokensFromCodeableConcept(List<String> theSystems, List<String> theCodes, CodeableConcept theCodeableConcept, ResourceTable theEntity,
|
||||
Set<BaseResourceIndexedSearchParam> theListToPopulate, RuntimeSearchParam theParameterDef) {
|
||||
Set<BaseResourceIndexedSearchParam> theListToPopulate, RuntimeSearchParam theParameterDef) {
|
||||
for (Coding nextCoding : theCodeableConcept.getCoding()) {
|
||||
extractTokensFromCoding(theSystems, theCodes, theEntity, theListToPopulate, theParameterDef, nextCoding);
|
||||
}
|
||||
}
|
||||
|
||||
private void extractTokensFromCoding(List<String> theSystems, List<String> theCodes, ResourceTable theEntity, Set<BaseResourceIndexedSearchParam> theListToPopulate,
|
||||
RuntimeSearchParam theParameterDef, Coding nextCoding) {
|
||||
RuntimeSearchParam theParameterDef, Coding nextCoding) {
|
||||
if (nextCoding != null && !nextCoding.isEmpty()) {
|
||||
|
||||
String nextSystem = nextCoding.getSystemElement().getValueAsString();
|
||||
|
@ -691,16 +710,18 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
|
|||
FHIRPathEngine fp = new FHIRPathEngine(worker);
|
||||
|
||||
List<Object> values = new ArrayList<>();
|
||||
try {
|
||||
String[] nextPathsSplit = SPLIT.split(thePaths);
|
||||
for (String nextPath : nextPathsSplit) {
|
||||
List<Base> allValues = fp.evaluate((Base) theResource, nextPath);
|
||||
if (allValues.isEmpty() == false) {
|
||||
values.addAll(allValues);
|
||||
}
|
||||
String[] nextPathsSplit = SPLIT.split(thePaths);
|
||||
for (String nextPath : nextPathsSplit) {
|
||||
List<Base> allValues;
|
||||
try {
|
||||
allValues = fp.evaluate((Base) theResource, nextPath);
|
||||
} catch (FHIRException e) {
|
||||
String msg = getContext().getLocalizer().getMessage(BaseSearchParamExtractor.class, "failedToExtractPaths", nextPath, e.toString());
|
||||
throw new InternalErrorException(msg, e);
|
||||
}
|
||||
if (allValues.isEmpty() == false) {
|
||||
values.addAll(allValues);
|
||||
}
|
||||
} catch (FHIRException e) {
|
||||
throw new InternalErrorException(e);
|
||||
}
|
||||
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
|
@ -715,23 +736,6 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
|
|||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PathAndRef> extractResourceLinks(IBaseResource theResource, RuntimeSearchParam theNextSpDef) {
|
||||
ArrayList<PathAndRef> retVal = new ArrayList<>();
|
||||
|
||||
String[] nextPathsSplit = SPLIT.split(theNextSpDef.getPath());
|
||||
for (String path : nextPathsSplit) {
|
||||
path = path.trim();
|
||||
if (isNotBlank(path)) {
|
||||
for (Object next : extractValues(path, theResource)) {
|
||||
retVal.add(new PathAndRef(path, next));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setValidationSupportForTesting(org.hl7.fhir.r4.hapi.ctx.IValidationSupport theValidationSupport) {
|
||||
myValidationSupport = theValidationSupport;
|
||||
|
|
|
@ -23,6 +23,9 @@ package ca.uhn.fhir.jpa.entity;
|
|||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.parser.IParser;
|
||||
|
||||
/**
|
||||
* @see ResourceHistoryTable#ENCODING_COL_LENGTH
|
||||
*/
|
||||
public enum ResourceEncodingEnum {
|
||||
|
||||
/*
|
||||
|
|
|
@ -43,6 +43,10 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl
|
|||
|
||||
private static final long serialVersionUID = 1L;
|
||||
public static final String IDX_RESVER_ID_VER = "IDX_RESVER_ID_VER";
|
||||
/**
|
||||
* @see ResourceEncodingEnum
|
||||
*/
|
||||
public static final int ENCODING_COL_LENGTH = 5;
|
||||
|
||||
@Id
|
||||
@SequenceGenerator(name = "SEQ_RESOURCE_HISTORY_ID", sequenceName = "SEQ_RESOURCE_HISTORY_ID")
|
||||
|
@ -67,7 +71,7 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl
|
|||
@OptimisticLock(excluded = true)
|
||||
private byte[] myResource;
|
||||
|
||||
@Column(name = "RES_ENCODING", nullable = false, length = 5)
|
||||
@Column(name = "RES_ENCODING", nullable = false, length = ENCODING_COL_LENGTH)
|
||||
@Enumerated(EnumType.STRING)
|
||||
@OptimisticLock(excluded = true)
|
||||
private ResourceEncodingEnum myEncoding;
|
||||
|
|
|
@ -51,6 +51,7 @@ public class Search implements Serializable {
|
|||
|
||||
private static final long serialVersionUID = 1L;
|
||||
public static final int MAX_SEARCH_QUERY_STRING = 10000;
|
||||
public static final int UUID_COLUMN_LENGTH = 36;
|
||||
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
@Column(name="CREATED", nullable=false, updatable=false)
|
||||
|
@ -118,7 +119,7 @@ public class Search implements Serializable {
|
|||
@Column(name="TOTAL_COUNT", nullable=true)
|
||||
private Integer myTotalCount;
|
||||
|
||||
@Column(name="SEARCH_UUID", length=40, nullable=false, updatable=false)
|
||||
@Column(name="SEARCH_UUID", length= UUID_COLUMN_LENGTH, nullable=false, updatable=false)
|
||||
private String myUuid;
|
||||
|
||||
/**
|
||||
|
|
|
@ -61,6 +61,7 @@ import org.springframework.transaction.support.TransactionCallback;
|
|||
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.EntityManager;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
@ -408,7 +409,11 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
myManagedTxManager = theTxManager;
|
||||
}
|
||||
|
||||
static Pageable toPage(final int theFromIndex, int theToIndex) {
|
||||
/**
|
||||
* Creates a {@link Pageable} using a start and end index
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public static @Nullable Pageable toPage(final int theFromIndex, int theToIndex) {
|
||||
int pageSize = theToIndex - theFromIndex;
|
||||
if (pageSize < 1) {
|
||||
return null;
|
||||
|
|
|
@ -62,6 +62,9 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
|
|||
@Qualifier("mySearchParameterDaoDstu2")
|
||||
protected IFhirResourceDao<SearchParameter> mySearchParameterDao;
|
||||
@Autowired
|
||||
@Qualifier("myCommunicationDaoDstu2")
|
||||
protected IFhirResourceDao<Communication> myCommunicationDao;
|
||||
@Autowired
|
||||
@Qualifier("myBundleDaoDstu2")
|
||||
protected IFhirResourceDao<Bundle> myBundleDao;
|
||||
@Autowired
|
||||
|
|
|
@ -13,14 +13,12 @@ import ca.uhn.fhir.model.dstu2.valueset.*;
|
|||
import ca.uhn.fhir.model.primitive.*;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.*;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.*;
|
||||
import org.mockito.internal.util.collections.ListUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -37,6 +35,12 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
|
|||
myDaoConfig.setDefaultSearchParamsCanBeOverridden(new DaoConfig().isDefaultSearchParamsCanBeOverridden());
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
myDaoConfig.setValidateSearchParameterExpressionsOnSave(new DaoConfig().isValidateSearchParameterExpressionsOnSave());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreateInvalidNoBase() {
|
||||
SearchParameter fooSp = new SearchParameter();
|
||||
|
@ -53,6 +57,31 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndexFailsIfInvalidSearchParameterExists() {
|
||||
myDaoConfig.setValidateSearchParameterExpressionsOnSave(false);
|
||||
|
||||
SearchParameter threadIdSp = new SearchParameter();
|
||||
threadIdSp.setBase(ResourceTypeEnum.COMMUNICATION);
|
||||
threadIdSp.setCode("has-attachments");
|
||||
threadIdSp.setType(SearchParamTypeEnum.REFERENCE);
|
||||
threadIdSp.setXpath("Communication.payload[1].contentAttachment is not null");
|
||||
threadIdSp.setXpathUsage(XPathUsageTypeEnum.NORMAL);
|
||||
threadIdSp.setStatus(ConformanceResourceStatusEnum.ACTIVE);
|
||||
mySearchParameterDao.create(threadIdSp, mySrd);
|
||||
mySearchParamRegsitry.forceRefresh();
|
||||
|
||||
Communication com = new Communication();
|
||||
com.setStatus(CommunicationStatusEnum.IN_PROGRESS);
|
||||
try {
|
||||
myCommunicationDao.create(com, mySrd);
|
||||
fail();
|
||||
} catch (InternalErrorException e) {
|
||||
assertThat(e.getMessage(), startsWith("Failed to extract values from resource using FHIRPath \"Communication.payload[1].contentAttachment is not null\": ca.uhn"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreateInvalidParamInvalidResourceName() {
|
||||
SearchParameter fooSp = new SearchParameter();
|
||||
|
|
|
@ -168,6 +168,9 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
|
|||
@Qualifier("myPatientDaoDstu3")
|
||||
protected IFhirResourceDaoPatient<Patient> myPatientDao;
|
||||
@Autowired
|
||||
@Qualifier("myCommunicationDaoDstu3")
|
||||
protected IFhirResourceDao<Communication> myCommunicationDao;
|
||||
@Autowired
|
||||
@Qualifier("myPractitionerDaoDstu3")
|
||||
protected IFhirResourceDao<Practitioner> myPractitionerDao;
|
||||
@Autowired
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package ca.uhn.fhir.jpa.dao.dstu3;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||
import ca.uhn.fhir.model.api.Include;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.*;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
|
@ -12,6 +14,7 @@ import org.hl7.fhir.dstu3.model.Appointment.AppointmentStatus;
|
|||
import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
@ -29,6 +32,11 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
|
|||
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
myDaoConfig.setValidateSearchParameterExpressionsOnSave(new DaoConfig().isValidateSearchParameterExpressionsOnSave());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateInvalidNoBase() {
|
||||
SearchParameter fooSp = new SearchParameter();
|
||||
|
@ -211,6 +219,47 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndexFailsIfInvalidSearchParameterExists() {
|
||||
myDaoConfig.setValidateSearchParameterExpressionsOnSave(false);
|
||||
|
||||
SearchParameter threadIdSp = new SearchParameter();
|
||||
threadIdSp.addBase("Communication");
|
||||
threadIdSp.setCode("has-attachments");
|
||||
threadIdSp.setType(Enumerations.SearchParamType.REFERENCE);
|
||||
threadIdSp.setExpression("Communication.payload[1].contentAttachment is not null");
|
||||
threadIdSp.setXpathUsage(SearchParameter.XPathUsageType.NORMAL);
|
||||
threadIdSp.setStatus(Enumerations.PublicationStatus.ACTIVE);
|
||||
mySearchParameterDao.create(threadIdSp, mySrd);
|
||||
mySearchParamRegsitry.forceRefresh();
|
||||
|
||||
Communication com = new Communication();
|
||||
com.setStatus(Communication.CommunicationStatus.INPROGRESS);
|
||||
try {
|
||||
myCommunicationDao.create(com, mySrd);
|
||||
fail();
|
||||
} catch (InternalErrorException e) {
|
||||
assertThat(e.getMessage(), startsWith("Failed to extract values from resource using FHIRPath \"Communication.payload[1].contentAttachment is not null\": org.hl7.fhir"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRejectSearchParamWithInvalidExpression() {
|
||||
SearchParameter threadIdSp = new SearchParameter();
|
||||
threadIdSp.addBase("Communication");
|
||||
threadIdSp.setCode("has-attachments");
|
||||
threadIdSp.setType(Enumerations.SearchParamType.REFERENCE);
|
||||
threadIdSp.setExpression("Communication.payload[1].contentAttachment is not null");
|
||||
threadIdSp.setXpathUsage(SearchParameter.XPathUsageType.NORMAL);
|
||||
threadIdSp.setStatus(Enumerations.PublicationStatus.ACTIVE);
|
||||
try {
|
||||
mySearchParameterDao.create(threadIdSp, mySrd);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertThat(e.getMessage(), startsWith("The expression \"Communication.payload[1].contentAttachment is not null\" can not be evaluated and may be invalid: "));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See #863
|
||||
*/
|
||||
|
|
|
@ -16,6 +16,7 @@ import ca.uhn.fhir.rest.param.TokenParam;
|
|||
import ca.uhn.fhir.rest.server.exceptions.*;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import com.google.common.base.Charsets;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.hl7.fhir.dstu3.model.*;
|
||||
import org.hl7.fhir.dstu3.model.Bundle.*;
|
||||
|
@ -152,36 +153,6 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
|
|||
return input;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionUpdateTwoResourcesWithSameId() {
|
||||
Bundle request = new Bundle();
|
||||
|
||||
Patient p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue("DDD");
|
||||
p.setId("Patient/ABC");
|
||||
request.addEntry()
|
||||
.setResource(p)
|
||||
.getRequest()
|
||||
.setMethod(HTTPVerb.PUT)
|
||||
.setUrl("Patient/ABC");
|
||||
|
||||
p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue("DDD");
|
||||
p.setId("Patient/ABC");
|
||||
request.addEntry()
|
||||
.setResource(p)
|
||||
.getRequest()
|
||||
.setMethod(HTTPVerb.PUT)
|
||||
.setUrl("Patient/ABC");
|
||||
|
||||
try {
|
||||
mySystemDao.transaction(mySrd, request);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertThat(e.getMessage(), containsString("Transaction bundle contains multiple resources with ID: Patient/ABC"));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends org.hl7.fhir.dstu3.model.Resource> T find(Bundle theBundle, Class<T> theType, int theIndex) {
|
||||
int count = 0;
|
||||
|
@ -470,6 +441,17 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See #1044
|
||||
*/
|
||||
@Test
|
||||
public void testStructureDefinitionInBundle() throws IOException {
|
||||
String input = IOUtils.toString(FhirSystemDaoDstu3Test.class.getResourceAsStream("/bug1044-bundle.xml"), Charsets.UTF_8);
|
||||
Bundle inputBundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, input);
|
||||
|
||||
mySystemDao.transaction(mySrd, inputBundle);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSystemMetaOperation() {
|
||||
|
||||
|
@ -2256,6 +2238,36 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionUpdateTwoResourcesWithSameId() {
|
||||
Bundle request = new Bundle();
|
||||
|
||||
Patient p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue("DDD");
|
||||
p.setId("Patient/ABC");
|
||||
request.addEntry()
|
||||
.setResource(p)
|
||||
.getRequest()
|
||||
.setMethod(HTTPVerb.PUT)
|
||||
.setUrl("Patient/ABC");
|
||||
|
||||
p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue("DDD");
|
||||
p.setId("Patient/ABC");
|
||||
request.addEntry()
|
||||
.setResource(p)
|
||||
.getRequest()
|
||||
.setMethod(HTTPVerb.PUT)
|
||||
.setUrl("Patient/ABC");
|
||||
|
||||
try {
|
||||
mySystemDao.transaction(mySrd, request);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertThat(e.getMessage(), containsString("Transaction bundle contains multiple resources with ID: Patient/ABC"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionWIthInvalidPlaceholder() throws Exception {
|
||||
Bundle res = new Bundle();
|
||||
|
|
|
@ -92,6 +92,9 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
|
|||
@Qualifier("myBundleDaoR4")
|
||||
protected IFhirResourceDao<Bundle> myBundleDao;
|
||||
@Autowired
|
||||
@Qualifier("myCommunicationDaoR4")
|
||||
protected IFhirResourceDao<Communication> myCommunicationDao;
|
||||
@Autowired
|
||||
@Qualifier("myCarePlanDaoR4")
|
||||
protected IFhirResourceDao<CarePlan> myCarePlanDao;
|
||||
@Autowired
|
||||
|
|
|
@ -6,6 +6,7 @@ import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken;
|
|||
import ca.uhn.fhir.model.api.Include;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.*;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
|
@ -13,6 +14,7 @@ import org.hl7.fhir.instance.model.api.IIdType;
|
|||
import org.hl7.fhir.r4.model.*;
|
||||
import org.hl7.fhir.r4.model.Appointment.AppointmentStatus;
|
||||
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
@ -30,6 +32,11 @@ import static org.junit.Assert.*;
|
|||
public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4SearchCustomSearchParamTest.class);
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
myDaoConfig.setValidateSearchParameterExpressionsOnSave(new DaoConfig().isValidateSearchParameterExpressionsOnSave());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeDisableResultReuse() {
|
||||
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
||||
|
@ -71,7 +78,6 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreateInvalidParamNoPath() {
|
||||
SearchParameter fooSp = new SearchParameter();
|
||||
|
@ -237,6 +243,30 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndexFailsIfInvalidSearchParameterExists() {
|
||||
myDaoConfig.setValidateSearchParameterExpressionsOnSave(false);
|
||||
|
||||
SearchParameter threadIdSp = new SearchParameter();
|
||||
threadIdSp.addBase("Communication");
|
||||
threadIdSp.setCode("has-attachments");
|
||||
threadIdSp.setType(Enumerations.SearchParamType.REFERENCE);
|
||||
threadIdSp.setExpression("Communication.payload[1].contentAttachment is not null");
|
||||
threadIdSp.setXpathUsage(SearchParameter.XPathUsageType.NORMAL);
|
||||
threadIdSp.setStatus(Enumerations.PublicationStatus.ACTIVE);
|
||||
mySearchParameterDao.create(threadIdSp, mySrd);
|
||||
mySearchParamRegsitry.forceRefresh();
|
||||
|
||||
Communication com = new Communication();
|
||||
com.setStatus(Communication.CommunicationStatus.INPROGRESS);
|
||||
try {
|
||||
myCommunicationDao.create(com, mySrd);
|
||||
fail();
|
||||
} catch (InternalErrorException e) {
|
||||
assertThat(e.getMessage(), startsWith("Failed to extract values from resource using FHIRPath \"Communication.payload[1].contentAttachment is not null\": org.hl7.fhir"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverrideAndDisableBuiltInSearchParametersWithOverridingDisabled() {
|
||||
myDaoConfig.setDefaultSearchParamsCanBeOverridden(false);
|
||||
|
@ -387,6 +417,23 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
|
|||
assertThat(results, contains(mrId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRejectSearchParamWithInvalidExpression() {
|
||||
SearchParameter threadIdSp = new SearchParameter();
|
||||
threadIdSp.addBase("Communication");
|
||||
threadIdSp.setCode("has-attachments");
|
||||
threadIdSp.setType(Enumerations.SearchParamType.REFERENCE);
|
||||
threadIdSp.setExpression("Communication.payload[1].contentAttachment is not null");
|
||||
threadIdSp.setXpathUsage(SearchParameter.XPathUsageType.NORMAL);
|
||||
threadIdSp.setStatus(Enumerations.PublicationStatus.ACTIVE);
|
||||
try {
|
||||
mySearchParameterDao.create(threadIdSp, mySrd);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertThat(e.getMessage(), startsWith("The expression \"Communication.payload[1].contentAttachment is not null\" can not be evaluated and may be invalid: "));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchForExtensionReferenceWithNonMatchingTarget() {
|
||||
SearchParameter siblingSp = new SearchParameter();
|
||||
|
|
|
@ -0,0 +1,214 @@
|
|||
<Bundle>
|
||||
<type value="transaction"/>
|
||||
<entry>
|
||||
<resource>
|
||||
<StructureDefinition xmlns="http://hl7.org/fhir">
|
||||
<meta>
|
||||
<lastUpdated value="2017-10-20T11:01:15.167+02:00"/>
|
||||
</meta>
|
||||
<url value="http://fhir.de/StructureDefinition/organization-de-basis/0.2"/>
|
||||
<version value="0.2-WORK"/>
|
||||
<name value="organization-de-basis-0.2"/>
|
||||
<title value="Organisation, deutsches Basisprofil (Version 0.2)"/>
|
||||
<status value="draft"/>
|
||||
<date value="2018-06-28"/>
|
||||
<publisher value="HL7 Deutschland e.V. (Technisches Komitee FHIR)"/>
|
||||
<contact>
|
||||
<telecom>
|
||||
<system value="other"/>
|
||||
<value value="http://hl7.de/technische-komitees/fhir/"/>
|
||||
</telecom>
|
||||
</contact>
|
||||
<description value="Basisprofil für die Verwendung der Organization Ressource in Deutschland."/>
|
||||
<copyright value="HL7 Deutschland e.V."/>
|
||||
<fhirVersion value="3.0.1"/>
|
||||
<kind value="resource"/>
|
||||
<abstract value="false"/>
|
||||
<type value="Organization"/>
|
||||
<baseDefinition value="http://hl7.org/fhir/StructureDefinition/Organization"/>
|
||||
<differential>
|
||||
<element id="Organization">
|
||||
<path value="Organization"/>
|
||||
<short value="Organisationen im deutschen Gesundheitswesen."/>
|
||||
<definition
|
||||
value="Basisprofil für die Repräsentation verschiedener Organisationen mit in Deutschland üblichen Identifiern."/>
|
||||
</element>
|
||||
<element id="Organization.extension">
|
||||
<path value="Organization.extension"/>
|
||||
<slicing>
|
||||
<discriminator>
|
||||
<type value="value"/>
|
||||
<path value="url"/>
|
||||
</discriminator>
|
||||
<rules value="open"/>
|
||||
</slicing>
|
||||
</element>
|
||||
<element id="Organization.extension:betriebsstaetten-hierarchie">
|
||||
<path value="Organization.extension"/>
|
||||
<sliceName value="betriebsstaetten-hierarchie"/>
|
||||
<max value="1"/>
|
||||
<type>
|
||||
<code value="Extension"/>
|
||||
<profile value="http://fhir.de/StructureDefinition/betriebsstaetten-hierarchie/0.2"/>
|
||||
</type>
|
||||
</element>
|
||||
<element id="Organization.identifier">
|
||||
<path value="Organization.identifier"/>
|
||||
<slicing>
|
||||
<discriminator>
|
||||
<type value="value"/>
|
||||
<path value="system"/>
|
||||
</discriminator>
|
||||
<discriminator>
|
||||
<type value="value"/>
|
||||
<path value="value"/>
|
||||
</discriminator>
|
||||
<rules value="open"/>
|
||||
</slicing>
|
||||
<short value="Identifiziert eine Organisation"/>
|
||||
<definition
|
||||
value="Identifikator für die Organisation, mit dem die Organisation über mehrere verschiedene Systeme hinweg identifiziert wird."/>
|
||||
</element>
|
||||
<element id="Organization.identifier.system">
|
||||
<path value="Organization.identifier.system"/>
|
||||
<min value="1"/>
|
||||
</element>
|
||||
<element id="Organization.identifier.value">
|
||||
<path value="Organization.identifier.value"/>
|
||||
<min value="1"/>
|
||||
</element>
|
||||
<element id="Organization.identifier:Betriebsstaettennummer">
|
||||
<path value="Organization.identifier"/>
|
||||
<sliceName value="Betriebsstaettennummer"/>
|
||||
<short value="Betriebstättennummer (BSNR) vergeben durch die KBV."/>
|
||||
<definition
|
||||
value="Die Betriebsstättennummer (BSNR) entspricht der bis zum 30. Juni 2008 gültigen siebenstelligen KV-Abrechnungsnummer, ergänzt um zwei angehängte Nullen. Sie identifiziert die Arztpraxis als abrechnende Einheit und ermöglicht die Zuordnung ärztlicher Leistungen zum Ort der Leistungserbringung. Dabei umfasst der Begriff Arztpraxis auch Medizinische Versorgungszentren (MVZ), Institute, Notfallambulanzen sowie Ermächtigungen an Krankenhäusern. Stellen 1–2: KV-Landes- oder Bezirksstellenschlüssel[1] Stellen 3–7: eindeutige Identifikationsnummer der KV, in deren Bereich die Betriebsstätte liegt Stellen 8, 9: „00“"/>
|
||||
<max value="1"/>
|
||||
</element>
|
||||
<element id="Organization.identifier:Betriebsstaettennummer.system">
|
||||
<path value="Organization.identifier.system"/>
|
||||
<short value="Namespace für Betriebsstättennnummern der KBV"/>
|
||||
<definition value="Die URL dient als eindeutiger Name des BSNR-Nummernkreises."/>
|
||||
<min value="1"/>
|
||||
<fixedUri value="http://fhir.de/NamingSystem/kbv/bsnr"/>
|
||||
</element>
|
||||
<element id="Organization.identifier:Betriebsstaettennummer.value">
|
||||
<path value="Organization.identifier.value"/>
|
||||
<short value="Betriebsstättennummer der Organisation"/>
|
||||
<definition value="Betriebsstättennummer der Organisation"/>
|
||||
<min value="1"/>
|
||||
</element>
|
||||
<element id="Organization.identifier:Betriebsstaettennummer.period">
|
||||
<path value="Organization.identifier.period"/>
|
||||
<short value="Zeitraum in welchem der Identifikator gültig ist oder war."/>
|
||||
<definition value="Zeitraum in welchem der Identifikator gültig ist oder war."/>
|
||||
</element>
|
||||
<element id="Organization.identifier:Betriebsstaettennummer.period.start">
|
||||
<path value="Organization.identifier.period.start"/>
|
||||
<short value="Beginn der BSNR Gültigkeit"/>
|
||||
<definition value="Beginn der BSNR Gültigkeit"/>
|
||||
</element>
|
||||
<element id="Organization.identifier:Betriebsstaettennummer.period.end">
|
||||
<path value="Organization.identifier.period.end"/>
|
||||
<short value="Ende der BSNR Gültigkeit"/>
|
||||
<definition value="Ende der BSNR Gültigkeit. leer, falls aktuell gültig."/>
|
||||
</element>
|
||||
<element id="Organization.identifier:Betriebsstaettennummer.assigner">
|
||||
<path value="Organization.identifier.assigner"/>
|
||||
<short value="Organisation welche den Identifikator vergeben hat."/>
|
||||
<definition value="Organisation welche den Identifikator vergeben hat."/>
|
||||
</element>
|
||||
<element id="Organization.identifier:Betriebsstaettennummer.assigner.display">
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable">
|
||||
<valueBoolean value="true"/>
|
||||
</extension>
|
||||
<path value="Organization.identifier.assigner.display"/>
|
||||
<short value="Name der zuständigen Kassenärztlichen Vereinigung (KV)"/>
|
||||
<definition
|
||||
value="Name der zuständigen Kassenärztlichen Vereinigung (KV). z.B.: "KV Baden-Württemberg""/>
|
||||
</element>
|
||||
<element id="Organization.identifier:Institutionskennzeichen">
|
||||
<path value="Organization.identifier"/>
|
||||
<sliceName value="Institutionskennzeichen"/>
|
||||
<short value="IK Nummer vergeben durch die Arbeitsgemeinschaft Institutionskennzeichen."/>
|
||||
<definition
|
||||
value="Die Institutionskennzeichen (kurz: IK) sind bundesweit eindeutige, neunstellige Zahlen vergeben durch die Arbeitsgemeinschaft Institutionskennzeichen, mit deren Hilfe Abrechnungen und Qualitätssicherungsmaßnahmen im Bereich der deutschen Sozialversicherung einrichtungsübergreifend abgewickelt werden können. "/>
|
||||
<max value="1"/>
|
||||
</element>
|
||||
<element id="Organization.identifier:Institutionskennzeichen.system">
|
||||
<path value="Organization.identifier.system"/>
|
||||
<short value="Namespace für Instituskennzeichen."/>
|
||||
<min value="1"/>
|
||||
<fixedUri value=" http://fhir.de/NamingSystem/arge-ik/iknr"/>
|
||||
</element>
|
||||
<element id="Organization.identifier:Institutionskennzeichen.value">
|
||||
<path value="Organization.identifier.value"/>
|
||||
<short value="Institutskennzeichen der Organisation"/>
|
||||
<definition value="Institutskennzeichen der Organisation"/>
|
||||
<min value="1"/>
|
||||
</element>
|
||||
<element id="Organization.identifier:ASV-Teamnummer">
|
||||
<path value="Organization.identifier"/>
|
||||
<sliceName value="ASV-Teamnummer"/>
|
||||
<short value="Die ASV-Teamnummer"/>
|
||||
<definition value="ASV-Teamnummer. Wird nur für Organizations vom Typ ASV-Team vergeben."/>
|
||||
<max value="1"/>
|
||||
</element>
|
||||
<element id="Organization.identifier:ASV-Teamnummer.system">
|
||||
<path value="Organization.identifier.system"/>
|
||||
<min value="1"/>
|
||||
<fixedUri value="http://fhir.de/NamingSystem/asv/teamnummer"/>
|
||||
</element>
|
||||
<element id="Organization.identifier:ASV-Teamnummer.value">
|
||||
<path value="Organization.identifier.value"/>
|
||||
<min value="1"/>
|
||||
</element>
|
||||
<element id="Organization.type">
|
||||
<path value="Organization.type"/>
|
||||
<short value="Art(en) der Organisation"/>
|
||||
<definition value="Art(en) der Organisation"/>
|
||||
<binding>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName">
|
||||
<valueString value="OrganizationType"/>
|
||||
</extension>
|
||||
<strength value="extensible"/>
|
||||
<valueSetUri value="http://fhir.de/ValueSet/arge-ik/klassifikation"/>
|
||||
</binding>
|
||||
</element>
|
||||
<element id="Organization.type.coding">
|
||||
<path value="Organization.type.coding"/>
|
||||
<short value="IK Klassifikation der Organisation"/>
|
||||
<definition value="IK Klassifikation der Organisation"/>
|
||||
</element>
|
||||
<element id="Organization.name">
|
||||
<path value="Organization.name"/>
|
||||
<short value="Name der Betriebsstätte"/>
|
||||
<definition
|
||||
value="Menschenlesbarer Name der Betriebsstätte, z.B.: "Gemeinschaftspraxis Dr. Soundso""/>
|
||||
</element>
|
||||
<element id="Organization.address">
|
||||
<path value="Organization.address"/>
|
||||
<type>
|
||||
<code value="Address"/>
|
||||
<profile value="http://fhir.de/StructureDefinition/address-de-basis/0.2"/>
|
||||
</type>
|
||||
</element>
|
||||
<element id="Organization.address.state">
|
||||
<path value="Organization.address.state"/>
|
||||
<definition value="Name (oder Kürzel) des Bundeslandes."/>
|
||||
</element>
|
||||
<element id="Organization.partOf">
|
||||
<path value="Organization.partOf"/>
|
||||
<type>
|
||||
<code value="Reference"/>
|
||||
<targetProfile value="http://fhir.de/StructureDefinition/organization-de-basis/0.2"/>
|
||||
</type>
|
||||
</element>
|
||||
</differential>
|
||||
</StructureDefinition>
|
||||
</resource>
|
||||
<request>
|
||||
<method value="POST"/>
|
||||
</request>
|
||||
</entry>
|
||||
</Bundle>
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"resourceType": "Communication",
|
||||
"meta": {
|
||||
"lastUpdated": "2018-07-20T19:34:56.236+05:30",
|
||||
"tag": [
|
||||
{
|
||||
"system": "systemDefined",
|
||||
"code": "read"
|
||||
}
|
||||
]
|
||||
},
|
||||
"text": {
|
||||
"status": "generated"
|
||||
},
|
||||
"extension": [
|
||||
{
|
||||
"url": "http://telus.com/fhir/StructureDefinition/ext-communication-msgOwner",
|
||||
"valueString": "17427"
|
||||
},
|
||||
{
|
||||
"url": "http://telus.com/fhir/StructureDefinition/ext-communication-priority",
|
||||
"valueCode": "normal"
|
||||
},
|
||||
{
|
||||
"url": "http://telus.com/fhir/StructureDefinition/ext-communication-topic",
|
||||
"valueString": "dsads"
|
||||
},
|
||||
{
|
||||
"url": "http://telus.com/fhir/StructureDefinition/ext-communication-state",
|
||||
"valueCode": "draft"
|
||||
},
|
||||
{
|
||||
"url": "http://telus.com/fhir/StructureDefinition/ext-communication-thread-id",
|
||||
"valueString": "bd7bc833-953b-4379-b0b0-be3d898dee40"
|
||||
}
|
||||
],
|
||||
"status": "in-progress",
|
||||
"recipient": [
|
||||
{
|
||||
"reference": "RelatedPerson/17852"
|
||||
}
|
||||
],
|
||||
"sender": {
|
||||
"reference": "RelatedPerson/17427"
|
||||
}
|
||||
}
|
|
@ -1,6 +1,10 @@
|
|||
package ca.uhn.fhir.rest.api.server;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
|
@ -22,28 +26,108 @@ import java.util.Date;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
|
||||
public interface IBundleProvider {
|
||||
|
||||
/**
|
||||
* If this method is implemented, provides an ID for the current
|
||||
* page of results. This ID should be unique (at least within
|
||||
* the current search as identified by {@link #getUuid()})
|
||||
* so that it can be used to look up a specific page of results.
|
||||
* <p>
|
||||
* This can be used in order to allow the
|
||||
* server paging mechanism to work using completely
|
||||
* opaque links (links that do not encode any index/offset
|
||||
* information), which can be useful on some servers.
|
||||
* </p>
|
||||
*
|
||||
* @since 3.5.0
|
||||
*/
|
||||
default String getCurrentPageId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this method is implemented, provides an ID for the next
|
||||
* page of results. This ID should be unique (at least within
|
||||
* the current search as identified by {@link #getUuid()})
|
||||
* so that it can be used to look up a specific page of results.
|
||||
* <p>
|
||||
* This can be used in order to allow the
|
||||
* server paging mechanism to work using completely
|
||||
* opaque links (links that do not encode any index/offset
|
||||
* information), which can be useful on some servers.
|
||||
* </p>
|
||||
*
|
||||
* @since 3.5.0
|
||||
*/
|
||||
default String getNextPageId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this method is implemented, provides an ID for the previous
|
||||
* page of results. This ID should be unique (at least within
|
||||
* the current search as identified by {@link #getUuid()})
|
||||
* so that it can be used to look up a specific page of results.
|
||||
* <p>
|
||||
* This can be used in order to allow the
|
||||
* server paging mechanism to work using completely
|
||||
* opaque links (links that do not encode any index/offset
|
||||
* information), which can be useful on some servers.
|
||||
* </p>
|
||||
*
|
||||
* @since 3.5.0
|
||||
*/
|
||||
default String getPreviousPageId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the instant as of which this result was created. The
|
||||
* result of this value is used to populate the <code>lastUpdated</code>
|
||||
* value on search result/history result bundles.
|
||||
*/
|
||||
IPrimitiveType<Date> getPublished();
|
||||
|
||||
/**
|
||||
* Load the given collection of resources by index, plus any additional resources per the
|
||||
* server's processing rules (e.g. _include'd resources, OperationOutcome, etc.). For example,
|
||||
* if the method is invoked with index 0,10 the method might return 10 search results, plus an
|
||||
* additional 20 resources which matched a client's _include specification.
|
||||
* <p>
|
||||
* Note that if this bundle provider was loaded using a
|
||||
* page ID (i.e. via {@link ca.uhn.fhir.rest.server.IPagingProvider#retrieveResultList(String, String)}
|
||||
* because {@link #getNextPageId()} provided a value on the
|
||||
* previous page, then the indexes should be ignored and the
|
||||
* whole page returned.
|
||||
* </p>
|
||||
*
|
||||
* @param theFromIndex
|
||||
* The low index (inclusive) to return
|
||||
* @param theToIndex
|
||||
* The high index (exclusive) to return
|
||||
* @param theFromIndex The low index (inclusive) to return
|
||||
* @param theToIndex The high index (exclusive) to return
|
||||
* @return A list of resources. The size of this list must be at least <code>theToIndex - theFromIndex</code>.
|
||||
*/
|
||||
List<IBaseResource> getResources(int theFromIndex, int theToIndex);
|
||||
|
||||
/**
|
||||
* Returns the UUID associated with this search. Note that this
|
||||
* does not need to return a non-null value unless it a
|
||||
* IPagingProvider is being used that requires UUIDs
|
||||
* being returned.
|
||||
* <p>
|
||||
* In other words, if you are using the default FifoMemoryPagingProvider in
|
||||
* your server, it is fine for this method to simply return {@code null} since FifoMemoryPagingProvider
|
||||
* does not use the value anyhow. On the other hand, if you are creating a custom
|
||||
* IPagingProvider implementation you might use this method to communicate
|
||||
* the search ID back to the provider.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that the UUID returned by this method corresponds to
|
||||
* the search, and not to the individual page.
|
||||
* </p>
|
||||
*/
|
||||
String getUuid();
|
||||
|
||||
/**
|
||||
* Optionally may be used to signal a preferred page size to the server, e.g. because
|
||||
* the implementing code recognizes that the resources which will be returned by this
|
||||
|
@ -62,24 +146,4 @@ public interface IBundleProvider {
|
|||
*/
|
||||
Integer size();
|
||||
|
||||
/**
|
||||
* Returns the instant as of which this result was valid
|
||||
*/
|
||||
IPrimitiveType<Date> getPublished();
|
||||
|
||||
/**
|
||||
* Returns the UUID associated with this search. Note that this
|
||||
* does not need to return a non-null value unless it a
|
||||
* IPagingProvider is being used that requires UUIDs
|
||||
* being returned.
|
||||
* <p>
|
||||
* In other words, if you are using the default FifoMemoryPagingProvider in
|
||||
* your server, it is fine for this method to simply return {@code null} since FifoMemoryPagingProvider
|
||||
* does not use the value anyhow. On the other hand, if you are creating a custom
|
||||
* IPagingProvider implementation you might use this method to communicate
|
||||
* the search ID back to the provider.
|
||||
* </p>
|
||||
*/
|
||||
public String getUuid();
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Server Framework
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2018 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Bundle provider that uses named pages instead of counts
|
||||
*/
|
||||
public class BundleProviderWithNamedPages extends SimpleBundleProvider {
|
||||
|
||||
private String myNextPageId;
|
||||
private String myCurrentPageId;
|
||||
private String myPreviousPageId;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param theResultsInThisPage The complete list of results in the current page. Must not be null.
|
||||
* @param theSearchId The ID for the search. Note that you should also populate {@link #setNextPageId(String)} and {@link #setPreviousPageId(String)} if these are known. Must not be <code>null</code> or blank.
|
||||
* @param thePageId The ID for the current page. Note that you should also populate {@link #setNextPageId(String)} and {@link #setPreviousPageId(String)} if these are known. Must not be <code>null</code> or blank.
|
||||
* @param theTotalResults The total number of result (if this is known), or <code>null</code>
|
||||
* @see #setNextPageId(String)
|
||||
* @see #setPreviousPageId(String)
|
||||
*/
|
||||
public BundleProviderWithNamedPages(List<IBaseResource> theResultsInThisPage, String theSearchId, String thePageId, Integer theTotalResults) {
|
||||
super(theResultsInThisPage, theSearchId);
|
||||
|
||||
Validate.notNull(theResultsInThisPage, "theResultsInThisPage must not be null");
|
||||
Validate.notBlank(thePageId, "thePageId must not be null or blank");
|
||||
|
||||
setCurrentPageId(thePageId);
|
||||
setSize(theTotalResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentPageId() {
|
||||
return myCurrentPageId;
|
||||
}
|
||||
|
||||
public BundleProviderWithNamedPages setCurrentPageId(String theCurrentPageId) {
|
||||
myCurrentPageId = theCurrentPageId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNextPageId() {
|
||||
return myNextPageId;
|
||||
}
|
||||
|
||||
public BundleProviderWithNamedPages setNextPageId(String theNextPageId) {
|
||||
myNextPageId = theNextPageId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPreviousPageId() {
|
||||
return myPreviousPageId;
|
||||
}
|
||||
|
||||
public BundleProviderWithNamedPages setPreviousPageId(String thePreviousPageId) {
|
||||
myPreviousPageId = thePreviousPageId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
|
||||
return getList(); // indexes are ignored for this provider type
|
||||
}
|
||||
|
||||
@Override
|
||||
public BundleProviderWithNamedPages setSize(Integer theSize) {
|
||||
super.setSize(theSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -29,13 +29,20 @@ public interface IPagingProvider {
|
|||
int getMaximumPageSize();
|
||||
|
||||
/**
|
||||
* Stores a result list and returns an ID with which that list can be returned
|
||||
* Retrieve a result list by ID
|
||||
*/
|
||||
public String storeResultList(IBundleProvider theList);
|
||||
IBundleProvider retrieveResultList(String theSearchId);
|
||||
|
||||
/**
|
||||
* Retrieve a result list by ID
|
||||
*/
|
||||
public IBundleProvider retrieveResultList(String theId);
|
||||
default IBundleProvider retrieveResultList(String theSearchId, String thePageId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a result list and returns an ID with which that list can be returned
|
||||
*/
|
||||
String storeResultList(IBundleProvider theList);
|
||||
|
||||
}
|
||||
|
|
|
@ -122,7 +122,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
|||
*/
|
||||
private String myServerVersion = createPoweredByHeaderProductVersion();
|
||||
private boolean myStarted;
|
||||
private Map<String, IResourceProvider> myTypeToProvider = new HashMap<>();
|
||||
private boolean myUncompressIncomingContents = true;
|
||||
private boolean myUseBrowserFriendlyContentTypes;
|
||||
private ITenantIdentificationStrategy myTenantIdentificationStrategy;
|
||||
|
@ -376,7 +375,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
|||
try {
|
||||
count += findResourceMethods(theProvider, clazz);
|
||||
} catch (ConfigurationException e) {
|
||||
throw new ConfigurationException("Failure scanning class " + clazz.getSimpleName() + ": " + e.getMessage());
|
||||
throw new ConfigurationException("Failure scanning class " + clazz.getSimpleName() + ": " + e.getMessage(), e);
|
||||
}
|
||||
if (count == 0) {
|
||||
throw new ConfigurationException("Did not find any annotated RESTful methods on provider class " + theProvider.getClass().getCanonicalName());
|
||||
|
@ -1365,14 +1364,9 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
|||
throw new NullPointerException("getResourceType() on class '" + rsrcProvider.getClass().getCanonicalName() + "' returned null");
|
||||
}
|
||||
String resourceName = getFhirContext().getResourceDefinition(resourceType).getName();
|
||||
if (myTypeToProvider.containsKey(resourceName)) {
|
||||
throw new ConfigurationException("Multiple resource providers return resource type[" + resourceName + "]: First[" + myTypeToProvider.get(resourceName).getClass().getCanonicalName()
|
||||
+ "] and Second[" + rsrcProvider.getClass().getCanonicalName() + "]");
|
||||
}
|
||||
if (!inInit) {
|
||||
myResourceProviders.add(rsrcProvider);
|
||||
}
|
||||
myTypeToProvider.put(resourceName, rsrcProvider);
|
||||
providedResourceScanner.scanForProvidedResources(rsrcProvider);
|
||||
newResourceProviders.add(rsrcProvider);
|
||||
} else {
|
||||
|
@ -1384,7 +1378,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
|||
|
||||
}
|
||||
if (!newResourceProviders.isEmpty()) {
|
||||
ourLog.info("Added {} resource provider(s). Total {}", newResourceProviders.size(), myTypeToProvider.size());
|
||||
ourLog.info("Added {} resource provider(s). Total {}", newResourceProviders.size(), myResourceProviders.size());
|
||||
for (IResourceProvider provider : newResourceProviders) {
|
||||
assertProviderIsValid(provider);
|
||||
findResourceMethods(provider);
|
||||
|
@ -1594,7 +1588,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
|||
IResourceProvider rsrcProvider = (IResourceProvider) provider;
|
||||
Class<? extends IBaseResource> resourceType = rsrcProvider.getResourceType();
|
||||
String resourceName = getFhirContext().getResourceDefinition(resourceType).getName();
|
||||
myTypeToProvider.remove(resourceName);
|
||||
providedResourceScanner.removeProvidedResources(rsrcProvider);
|
||||
} else {
|
||||
myPlainProviders.remove(provider);
|
||||
|
|
|
@ -130,6 +130,18 @@ public class RestfulServerUtils {
|
|||
|
||||
public static String createPagingLink(Set<Include> theIncludes, String theServerBase, String theSearchId, int theOffset, int theCount, Map<String, String[]> theRequestParameters, boolean thePrettyPrint,
|
||||
BundleTypeEnum theBundleType) {
|
||||
return createPagingLink(theIncludes, theServerBase, theSearchId, theOffset, theCount, theRequestParameters, thePrettyPrint,
|
||||
theBundleType, null);
|
||||
}
|
||||
|
||||
public static String createPagingLink(Set<Include> theIncludes, String theServerBase, String theSearchId, String thePageId, Map<String, String[]> theRequestParameters, boolean thePrettyPrint,
|
||||
BundleTypeEnum theBundleType) {
|
||||
return createPagingLink(theIncludes, theServerBase, theSearchId, null, null, theRequestParameters, thePrettyPrint,
|
||||
theBundleType, thePageId);
|
||||
}
|
||||
|
||||
private static String createPagingLink(Set<Include> theIncludes, String theServerBase, String theSearchId, Integer theOffset, Integer theCount, Map<String, String[]> theRequestParameters, boolean thePrettyPrint,
|
||||
BundleTypeEnum theBundleType, String thePageId) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append(theServerBase);
|
||||
b.append('?');
|
||||
|
@ -137,14 +149,24 @@ public class RestfulServerUtils {
|
|||
b.append('=');
|
||||
b.append(UrlUtil.escapeUrlParam(theSearchId));
|
||||
|
||||
b.append('&');
|
||||
b.append(Constants.PARAM_PAGINGOFFSET);
|
||||
b.append('=');
|
||||
b.append(theOffset);
|
||||
b.append('&');
|
||||
b.append(Constants.PARAM_COUNT);
|
||||
b.append('=');
|
||||
b.append(theCount);
|
||||
if (theOffset != null) {
|
||||
b.append('&');
|
||||
b.append(Constants.PARAM_PAGINGOFFSET);
|
||||
b.append('=');
|
||||
b.append(theOffset);
|
||||
}
|
||||
if (theCount != null) {
|
||||
b.append('&');
|
||||
b.append(Constants.PARAM_COUNT);
|
||||
b.append('=');
|
||||
b.append(theCount);
|
||||
}
|
||||
if (isNotBlank(thePageId)) {
|
||||
b.append('&');
|
||||
b.append(Constants.PARAM_PAGEID);
|
||||
b.append('=');
|
||||
b.append(UrlUtil.escapeUrlParam(thePageId));
|
||||
}
|
||||
String[] strings = theRequestParameters.get(Constants.PARAM_FORMAT);
|
||||
if (strings != null && strings.length > 0) {
|
||||
b.append('&');
|
||||
|
@ -442,6 +464,18 @@ public class RestfulServerUtils {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private static FhirContext getContextForVersion(FhirContext theContext, FhirVersionEnum theForVersion) {
|
||||
FhirContext context = theContext;
|
||||
if (context.getVersion().getVersion() != theForVersion) {
|
||||
context = myFhirContextMap.get(theForVersion);
|
||||
if (context == null) {
|
||||
context = theForVersion.newContext();
|
||||
myFhirContextMap.put(theForVersion, context);
|
||||
}
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
private static ResponseEncoding getEncodingForContentType(FhirContext theFhirContext, boolean theStrict, String theContentType) {
|
||||
EncodingEnum encoding;
|
||||
if (theStrict) {
|
||||
|
@ -476,18 +510,6 @@ public class RestfulServerUtils {
|
|||
return parser;
|
||||
}
|
||||
|
||||
private static FhirContext getContextForVersion(FhirContext theContext, FhirVersionEnum theForVersion) {
|
||||
FhirContext context = theContext;
|
||||
if (context.getVersion().getVersion() != theForVersion) {
|
||||
context = myFhirContextMap.get(theForVersion);
|
||||
if (context == null) {
|
||||
context = theForVersion.newContext();
|
||||
myFhirContextMap.put(theForVersion, context);
|
||||
}
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
public static Set<String> parseAcceptHeaderAndReturnHighestRankedOptions(HttpServletRequest theRequest) {
|
||||
Set<String> retVal = new HashSet<String>();
|
||||
|
||||
|
@ -725,7 +747,7 @@ public class RestfulServerUtils {
|
|||
try {
|
||||
return Integer.parseInt(retVal[0]);
|
||||
} catch (NumberFormatException e) {
|
||||
ourLog.debug("Failed to parse {} value '{}': {}", new Object[]{theParamName, retVal[0], e});
|
||||
ourLog.debug("Failed to parse {} value '{}': {}", new Object[] {theParamName, retVal[0], e});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,31 +20,62 @@ package ca.uhn.fhir.rest.server;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class SimpleBundleProvider implements IBundleProvider {
|
||||
|
||||
private List<IBaseResource> myList;
|
||||
|
||||
private final List<IBaseResource> myList;
|
||||
private final String myUuid;
|
||||
private Integer myPreferredPageSize;
|
||||
private Integer mySize;
|
||||
private IPrimitiveType<Date> myPublished = InstantDt.withCurrentTime();
|
||||
public SimpleBundleProvider(List<IBaseResource> theList) {
|
||||
myList = theList;
|
||||
this(theList, null);
|
||||
}
|
||||
|
||||
public SimpleBundleProvider(IBaseResource theResource) {
|
||||
myList = Collections.singletonList(theResource);
|
||||
this(Collections.singletonList(theResource));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an empty bundle
|
||||
*/
|
||||
public SimpleBundleProvider() {
|
||||
myList = Collections.emptyList();
|
||||
this(Collections.emptyList());
|
||||
}
|
||||
|
||||
public SimpleBundleProvider(List<IBaseResource> theList, String theUuid) {
|
||||
myList = theList;
|
||||
myUuid = theUuid;
|
||||
setSize(theList.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the results stored in this provider
|
||||
*/
|
||||
protected List<IBaseResource> getList() {
|
||||
return myList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPrimitiveType<Date> getPublished() {
|
||||
return myPublished;
|
||||
}
|
||||
|
||||
/**
|
||||
* By default this class uses the object creation date/time (for this object)
|
||||
* to determine {@link #getPublished() the published date} but this
|
||||
* method may be used to specify an alternate date/time
|
||||
*/
|
||||
public void setPublished(IPrimitiveType<Date> thePublished) {
|
||||
myPublished = thePublished;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -53,23 +84,38 @@ public class SimpleBundleProvider implements IBundleProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Integer size() {
|
||||
return myList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstantDt getPublished() {
|
||||
return InstantDt.withCurrentTime();
|
||||
public String getUuid() {
|
||||
return myUuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults to null
|
||||
*/
|
||||
@Override
|
||||
public Integer preferredPageSize() {
|
||||
return null;
|
||||
return myPreferredPageSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the preferred page size to be returned by {@link #preferredPageSize()}.
|
||||
* Default is <code>null</code>.
|
||||
*/
|
||||
public void setPreferredPageSize(Integer thePreferredPageSize) {
|
||||
myPreferredPageSize = thePreferredPageSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the total number of results, if this provider
|
||||
* corresponds to a single page within a larger search result
|
||||
*/
|
||||
public SimpleBundleProvider setSize(Integer theSize) {
|
||||
mySize = theSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUuid() {
|
||||
return null;
|
||||
public Integer size() {
|
||||
return mySize;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -123,8 +123,8 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
|
|||
|
||||
}
|
||||
|
||||
protected IBaseResource createBundleFromBundleProvider(IRestfulServer<?> theServer, RequestDetails theRequest, Integer theLimit, String theLinkSelf, Set<Include> theIncludes,
|
||||
IBundleProvider theResult, int theOffset, BundleTypeEnum theBundleType, EncodingEnum theLinkEncoding, String theSearchId) {
|
||||
IBaseResource createBundleFromBundleProvider(IRestfulServer<?> theServer, RequestDetails theRequest, Integer theLimit, String theLinkSelf, Set<Include> theIncludes,
|
||||
IBundleProvider theResult, int theOffset, BundleTypeEnum theBundleType, EncodingEnum theLinkEncoding, String theSearchId) {
|
||||
IVersionSpecificBundleFactory bundleFactory = theServer.getFhirContext().newBundleFactory();
|
||||
|
||||
int numToReturn;
|
||||
|
@ -152,7 +152,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
|
|||
numToReturn = Math.min(numToReturn, numTotalResults - theOffset);
|
||||
}
|
||||
|
||||
if (numToReturn > 0) {
|
||||
if (numToReturn > 0 || theResult.getCurrentPageId() != null) {
|
||||
resourceList = theResult.getResources(theOffset, numToReturn + theOffset);
|
||||
} else {
|
||||
resourceList = Collections.emptyList();
|
||||
|
@ -166,6 +166,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
|
|||
searchId = pagingProvider.storeResultList(theResult);
|
||||
if (isBlank(searchId)) {
|
||||
ourLog.info("Found {} results but paging provider did not provide an ID to use for paging", numTotalResults);
|
||||
searchId = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -183,11 +184,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
|
|||
}
|
||||
}
|
||||
if (hasNull) {
|
||||
for (Iterator<IBaseResource> iter = resourceList.iterator(); iter.hasNext(); ) {
|
||||
if (iter.next() == null) {
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
resourceList.removeIf(Objects::isNull);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -207,7 +204,18 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
|
|||
|
||||
String linkPrev = null;
|
||||
String linkNext = null;
|
||||
if (searchId != null) {
|
||||
|
||||
if (isNotBlank(theResult.getCurrentPageId())) {
|
||||
// We're doing named pages
|
||||
searchId = theResult.getUuid();
|
||||
if (isNotBlank(theResult.getNextPageId())) {
|
||||
linkNext = RestfulServerUtils.createPagingLink(theIncludes, serverBase, searchId, theResult.getNextPageId(), theRequest.getParameters(), prettyPrint, theBundleType);
|
||||
}
|
||||
if (isNotBlank(theResult.getPreviousPageId())) {
|
||||
linkPrev = RestfulServerUtils.createPagingLink(theIncludes, serverBase, searchId, theResult.getPreviousPageId(), theRequest.getParameters(), prettyPrint, theBundleType);
|
||||
}
|
||||
} else if (searchId != null) {
|
||||
// We're doing offset pages
|
||||
if (numTotalResults == null || theOffset + numToReturn < numTotalResults) {
|
||||
linkNext = (RestfulServerUtils.createPagingLink(theIncludes, serverBase, searchId, theOffset + numToReturn, numToReturn, theRequest.getParameters(), prettyPrint, theBundleType));
|
||||
}
|
||||
|
|
|
@ -19,16 +19,6 @@ package ca.uhn.fhir.rest.server.method;
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
|
@ -45,12 +35,22 @@ import ca.uhn.fhir.rest.param.ParameterUtil;
|
|||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
||||
|
||||
private final Integer myIdParamIndex;
|
||||
private String myResourceName;
|
||||
private final RestOperationTypeEnum myResourceOperationType;
|
||||
private String myResourceName;
|
||||
|
||||
public HistoryMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
|
||||
super(toReturnType(theMethod, theProvider), theMethod, theContext, theProvider);
|
||||
|
@ -87,13 +87,13 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
}
|
||||
|
||||
@Override
|
||||
public RestOperationTypeEnum getRestOperationType() {
|
||||
return myResourceOperationType;
|
||||
protected BundleTypeEnum getResponseBundleType() {
|
||||
return BundleTypeEnum.HISTORY;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BundleTypeEnum getResponseBundleType() {
|
||||
return BundleTypeEnum.HISTORY;
|
||||
public RestOperationTypeEnum getRestOperationType() {
|
||||
return myResourceOperationType;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -146,6 +146,21 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
*/
|
||||
return new IBundleProvider() {
|
||||
|
||||
@Override
|
||||
public String getCurrentPageId() {
|
||||
return resources.getCurrentPageId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNextPageId() {
|
||||
return resources.getNextPageId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPreviousPageId() {
|
||||
return resources.getPreviousPageId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPrimitiveType<Date> getPublished() {
|
||||
return resources.getPublished();
|
||||
|
@ -172,8 +187,8 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Integer size() {
|
||||
return resources.size();
|
||||
public String getUuid() {
|
||||
return resources.getUuid();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -182,8 +197,8 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getUuid() {
|
||||
return resources.getUuid();
|
||||
public Integer size() {
|
||||
return resources.size();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -20,18 +20,7 @@ package ca.uhn.fhir.rest.server.method;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.Include;
|
||||
|
@ -44,8 +33,22 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
|
|||
import ca.uhn.fhir.rest.param.binder.CollectionBinder;
|
||||
import ca.uhn.fhir.rest.server.method.OperationParameter.IOperationParamConverter;
|
||||
import ca.uhn.fhir.rest.server.method.ResourceParameter.Mode;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.util.ParametersUtil;
|
||||
import ca.uhn.fhir.util.ReflectionUtil;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public class MethodUtil {
|
||||
|
||||
|
@ -90,7 +93,26 @@ public class MethodUtil {
|
|||
}
|
||||
if (Collection.class.isAssignableFrom(parameterType)) {
|
||||
throw new ConfigurationException("Argument #" + paramIndex + " of Method '" + theMethod.getName() + "' in type '" + theMethod.getDeclaringClass().getCanonicalName()
|
||||
+ "' is of an invalid generic type (can not be a collection of a collection of a collection)");
|
||||
+ "' is of an invalid generic type (can not be a collection of a collection of a collection)");
|
||||
}
|
||||
|
||||
/*
|
||||
* If the user is trying to bind IPrimitiveType they are probably
|
||||
* trying to write code that is compatible across versions of FHIR.
|
||||
* We'll try and come up with an appropriate subtype to give
|
||||
* them.
|
||||
*
|
||||
* This gets tested in HistoryR4Test
|
||||
*/
|
||||
if (IPrimitiveType.class.equals(parameterType)) {
|
||||
Class<?> genericType = ReflectionUtil.getGenericCollectionTypeOfMethodParameter(theMethod, paramIndex);
|
||||
if (Date.class.equals(genericType)) {
|
||||
BaseRuntimeElementDefinition<?> dateTimeDef = theContext.getElementDefinition("dateTime");
|
||||
parameterType = dateTimeDef.getImplementingClass();
|
||||
} else if (String.class.equals(genericType) || genericType == null) {
|
||||
BaseRuntimeElementDefinition<?> dateTimeDef = theContext.getElementDefinition("string");
|
||||
parameterType = dateTimeDef.getImplementingClass();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,7 +163,7 @@ public class MethodUtil {
|
|||
specType = String.class;
|
||||
} else if ((parameterType != Include.class) || innerCollectionType == null || outerCollectionType != null) {
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' is annotated with @" + IncludeParam.class.getSimpleName() + " but has a type other than Collection<"
|
||||
+ Include.class.getSimpleName() + ">");
|
||||
+ Include.class.getSimpleName() + ">");
|
||||
} else {
|
||||
instantiableCollectionType = (Class<? extends Collection<Include>>) CollectionBinder.getInstantiableCollectionType(innerCollectionType, "Method '" + theMethod.getName() + "'");
|
||||
specType = parameterType;
|
||||
|
@ -198,7 +220,7 @@ public class MethodUtil {
|
|||
} else if (nextAnnotation instanceof Validate.Mode) {
|
||||
if (parameterType.equals(ValidationModeEnum.class) == false) {
|
||||
throw new ConfigurationException(
|
||||
"Parameter annotated with @" + Validate.class.getSimpleName() + "." + Validate.Mode.class.getSimpleName() + " must be of type " + ValidationModeEnum.class.getName());
|
||||
"Parameter annotated with @" + Validate.class.getSimpleName() + "." + Validate.Mode.class.getSimpleName() + " must be of type " + ValidationModeEnum.class.getName());
|
||||
}
|
||||
param = new OperationParameter(theContext, Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_MODE, 0, 1).setConverter(new IOperationParamConverter() {
|
||||
@Override
|
||||
|
@ -221,7 +243,7 @@ public class MethodUtil {
|
|||
} else if (nextAnnotation instanceof Validate.Profile) {
|
||||
if (parameterType.equals(String.class) == false) {
|
||||
throw new ConfigurationException(
|
||||
"Parameter annotated with @" + Validate.class.getSimpleName() + "." + Validate.Profile.class.getSimpleName() + " must be of type " + String.class.getName());
|
||||
"Parameter annotated with @" + Validate.class.getSimpleName() + "." + Validate.Profile.class.getSimpleName() + " must be of type " + String.class.getName());
|
||||
}
|
||||
param = new OperationParameter(theContext, Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_PROFILE, 0, 1).setConverter(new IOperationParamConverter() {
|
||||
@Override
|
||||
|
@ -244,8 +266,8 @@ public class MethodUtil {
|
|||
|
||||
if (param == null) {
|
||||
throw new ConfigurationException(
|
||||
"Parameter #" + ((paramIndex + 1)) + "/" + (parameterTypes.length) + " of method '" + theMethod.getName() + "' on type '" + theMethod.getDeclaringClass().getCanonicalName()
|
||||
+ "' has no recognized FHIR interface parameter annotations. Don't know how to handle this parameter");
|
||||
"Parameter #" + ((paramIndex + 1)) + "/" + (parameterTypes.length) + " of method '" + theMethod.getName() + "' on type '" + theMethod.getDeclaringClass().getCanonicalName()
|
||||
+ "' has no recognized FHIR interface parameter annotations. Don't know how to handle this parameter");
|
||||
}
|
||||
|
||||
param.initializeTypes(theMethod, outerCollectionType, innerCollectionType, parameterType);
|
||||
|
|
|
@ -20,23 +20,30 @@ package ca.uhn.fhir.rest.server.method;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.Include;
|
||||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.api.server.IRestfulServer;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.IPagingProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
||||
import ca.uhn.fhir.rest.server.RestfulServerUtils.ResponseEncoding;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.Include;
|
||||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.*;
|
||||
import ca.uhn.fhir.rest.api.server.*;
|
||||
import ca.uhn.fhir.rest.server.IPagingProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
||||
import ca.uhn.fhir.rest.server.RestfulServerUtils.ResponseEncoding;
|
||||
import ca.uhn.fhir.rest.server.exceptions.*;
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public class PageMethodBinding extends BaseResourceReturningMethodBinding {
|
||||
|
||||
|
@ -75,34 +82,51 @@ public class PageMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
if (pagingProvider == null) {
|
||||
throw new InvalidRequestException("This server does not support paging");
|
||||
}
|
||||
IBundleProvider resultList = pagingProvider.retrieveResultList(thePagingAction);
|
||||
|
||||
Integer offsetI;
|
||||
int start = 0;
|
||||
IBundleProvider resultList;
|
||||
|
||||
String pageId = null;
|
||||
String[] pageIdParams = theRequest.getParameters().get(Constants.PARAM_PAGEID);
|
||||
if (pageIdParams != null) {
|
||||
if (pageIdParams.length > 0) {
|
||||
if (isNotBlank(pageIdParams[0])) {
|
||||
pageId = pageIdParams[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pageId != null) {
|
||||
|
||||
resultList = pagingProvider.retrieveResultList(thePagingAction, pageId);
|
||||
|
||||
} else {
|
||||
|
||||
resultList = pagingProvider.retrieveResultList(thePagingAction);
|
||||
|
||||
offsetI = RestfulServerUtils.tryToExtractNamedParameter(theRequest, Constants.PARAM_PAGINGOFFSET);
|
||||
if (offsetI == null || offsetI < 0) {
|
||||
offsetI = 0;
|
||||
}
|
||||
|
||||
Integer totalNum = resultList.size();
|
||||
start = offsetI;
|
||||
if (totalNum != null) {
|
||||
start = Math.min(start, totalNum - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Return an HTTP 409 if the search is not known
|
||||
if (resultList == null) {
|
||||
ourLog.info("Client requested unknown paging ID[{}]", thePagingAction);
|
||||
String msg = getContext().getLocalizer().getMessage(PageMethodBinding.class, "unknownSearchId", thePagingAction);
|
||||
throw new ResourceGoneException(msg);
|
||||
}
|
||||
|
||||
Integer count = RestfulServerUtils.extractCountParameter(theRequest);
|
||||
if (count == null) {
|
||||
count = pagingProvider.getDefaultPageSize();
|
||||
} else if (count > pagingProvider.getMaximumPageSize()) {
|
||||
count = pagingProvider.getMaximumPageSize();
|
||||
}
|
||||
|
||||
Integer offsetI = RestfulServerUtils.tryToExtractNamedParameter(theRequest, Constants.PARAM_PAGINGOFFSET);
|
||||
if (offsetI == null || offsetI < 0) {
|
||||
offsetI = 0;
|
||||
}
|
||||
|
||||
Integer totalNum = resultList.size();
|
||||
int start = offsetI;
|
||||
if (totalNum != null) {
|
||||
start = Math.min(start, totalNum - 1);
|
||||
}
|
||||
|
||||
ResponseEncoding responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequest, theServer.getDefaultResponseEncoding());
|
||||
|
||||
Set<Include> includes = new HashSet<Include>();
|
||||
Set<Include> includes = new HashSet<>();
|
||||
String[] reqIncludes = theRequest.getParameters().get(Constants.PARAM_INCLUDE);
|
||||
if (reqIncludes != null) {
|
||||
for (String nextInclude : reqIncludes) {
|
||||
|
@ -126,6 +150,13 @@ public class PageMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
encodingEnum = responseEncoding.getEncoding();
|
||||
}
|
||||
|
||||
Integer count = RestfulServerUtils.extractCountParameter(theRequest);
|
||||
if (count == null) {
|
||||
count = pagingProvider.getDefaultPageSize();
|
||||
} else if (count > pagingProvider.getMaximumPageSize()) {
|
||||
count = pagingProvider.getMaximumPageSize();
|
||||
}
|
||||
|
||||
return createBundleFromBundleProvider(theServer, theRequest, count, linkSelf, includes, resultList, start, bundleType, encodingEnum, thePagingAction);
|
||||
}
|
||||
|
||||
|
@ -140,10 +171,7 @@ public class PageMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
if (pageId == null || pageId.length == 0 || isBlank(pageId[0])) {
|
||||
return false;
|
||||
}
|
||||
if (theRequest.getRequestType() != RequestTypeEnum.GET) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return theRequest.getRequestType() == RequestTypeEnum.GET;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -47,8 +47,8 @@ public class SearchParameter extends BaseQueryParameter {
|
|||
static final String QUALIFIER_ANY_TYPE = ":*";
|
||||
|
||||
static {
|
||||
ourParamTypes = new HashMap<Class<?>, RestSearchParameterTypeEnum>();
|
||||
ourParamQualifiers = new HashMap<RestSearchParameterTypeEnum, Set<String>>();
|
||||
ourParamTypes = new HashMap<>();
|
||||
ourParamQualifiers = new HashMap<>();
|
||||
|
||||
ourParamTypes.put(StringParam.class, RestSearchParameterTypeEnum.STRING);
|
||||
ourParamTypes.put(StringOrListParam.class, RestSearchParameterTypeEnum.STRING);
|
||||
|
@ -124,7 +124,7 @@ public class SearchParameter extends BaseQueryParameter {
|
|||
*/
|
||||
@Override
|
||||
public List<QualifiedParamList> encode(FhirContext theContext, Object theObject) throws InternalErrorException {
|
||||
ArrayList<QualifiedParamList> retVal = new ArrayList<QualifiedParamList>();
|
||||
ArrayList<QualifiedParamList> retVal = new ArrayList<>();
|
||||
|
||||
// TODO: declaring method should probably have a generic type..
|
||||
@SuppressWarnings("rawtypes")
|
||||
|
@ -197,7 +197,7 @@ public class SearchParameter extends BaseQueryParameter {
|
|||
}
|
||||
|
||||
public void setChainlists(String[] theChainWhitelist, String[] theChainBlacklist) {
|
||||
myQualifierWhitelist = new HashSet<String>(theChainWhitelist.length);
|
||||
myQualifierWhitelist = new HashSet<>(theChainWhitelist.length);
|
||||
myQualifierWhitelist.add(QUALIFIER_ANY_TYPE);
|
||||
|
||||
for (int i = 0; i < theChainWhitelist.length; i++) {
|
||||
|
@ -211,7 +211,7 @@ public class SearchParameter extends BaseQueryParameter {
|
|||
}
|
||||
|
||||
if (theChainBlacklist.length > 0) {
|
||||
myQualifierBlacklist = new HashSet<String>(theChainBlacklist.length);
|
||||
myQualifierBlacklist = new HashSet<>(theChainBlacklist.length);
|
||||
for (String next : theChainBlacklist) {
|
||||
if (next.equals(EMPTY_STRING)) {
|
||||
myQualifierBlacklist.add(EMPTY_STRING);
|
||||
|
@ -282,7 +282,7 @@ public class SearchParameter extends BaseQueryParameter {
|
|||
Set<String> builtInQualifiers = ourParamQualifiers.get(typeEnum);
|
||||
if (builtInQualifiers != null) {
|
||||
if (myQualifierWhitelist != null) {
|
||||
HashSet<String> qualifierWhitelist = new HashSet<String>();
|
||||
HashSet<String> qualifierWhitelist = new HashSet<>();
|
||||
qualifierWhitelist.addAll(myQualifierWhitelist);
|
||||
qualifierWhitelist.addAll(builtInQualifiers);
|
||||
myQualifierWhitelist = qualifierWhitelist;
|
||||
|
|
|
@ -20,7 +20,12 @@ package ca.uhn.fhir.rest.server.provider;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
||||
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||
import ca.uhn.fhir.rest.annotation.*;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.param.TokenAndListParam;
|
||||
|
@ -29,8 +34,10 @@ import ca.uhn.fhir.rest.param.TokenParam;
|
|||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -58,7 +65,9 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
|
|||
private final Class<T> myResourceType;
|
||||
private final FhirContext myFhirContext;
|
||||
private final String myResourceName;
|
||||
protected Map<String, TreeMap<Long, T>> myIdToVersionToResourceMap = new HashMap<>();
|
||||
protected Map<String, TreeMap<Long, T>> myIdToVersionToResourceMap = new LinkedHashMap<>();
|
||||
protected Map<String, LinkedList<T>> myIdToHistory = new LinkedHashMap<>();
|
||||
protected LinkedList<T> myTypeHistory = new LinkedList<>();
|
||||
private long myNextId;
|
||||
private AtomicLong myDeleteCount = new AtomicLong(0);
|
||||
private AtomicLong mySearchCount = new AtomicLong(0);
|
||||
|
@ -86,6 +95,8 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
|
|||
public void clear() {
|
||||
myNextId = 1;
|
||||
myIdToVersionToResourceMap.clear();
|
||||
myIdToHistory.clear();
|
||||
myTypeHistory.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -183,6 +194,21 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
|
|||
return myIdToVersionToResourceMap.get(theIdPart);
|
||||
}
|
||||
|
||||
@History
|
||||
public List<T> historyInstance(@IdParam IIdType theId) {
|
||||
LinkedList<T> retVal = myIdToHistory.get(theId.getIdPart());
|
||||
if (retVal == null) {
|
||||
throw new ResourceNotFoundException(theId);
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@History
|
||||
public List<T> historyType() {
|
||||
return myTypeHistory;
|
||||
}
|
||||
|
||||
@Read(version = true)
|
||||
public IBaseResource read(@IdParam IIdType theId) {
|
||||
TreeMap<Long, T> versions = myIdToVersionToResourceMap.get(theId.getIdPart());
|
||||
|
@ -213,8 +239,23 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
|
|||
}
|
||||
|
||||
@Search
|
||||
public List<IBaseResource> search(
|
||||
@OptionalParam(name = "_id") TokenAndListParam theIds) {
|
||||
public List<IBaseResource> searchAll() {
|
||||
List<IBaseResource> retVal = new ArrayList<>();
|
||||
|
||||
for (TreeMap<Long, T> next : myIdToVersionToResourceMap.values()) {
|
||||
if (next.isEmpty() == false) {
|
||||
T nextResource = next.lastEntry().getValue();
|
||||
retVal.add(nextResource);
|
||||
}
|
||||
}
|
||||
|
||||
mySearchCount.incrementAndGet();
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Search
|
||||
public List<IBaseResource> searchById(
|
||||
@RequiredParam(name = "_id") TokenAndListParam theIds) {
|
||||
|
||||
List<IBaseResource> retVal = new ArrayList<>();
|
||||
|
||||
|
@ -252,16 +293,52 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
|
|||
|
||||
private IIdType store(@ResourceParam T theResource, String theIdPart, Long theVersionIdPart) {
|
||||
IIdType id = myFhirContext.getVersion().newIdType();
|
||||
id.setParts(null, myResourceName, theIdPart, Long.toString(theVersionIdPart));
|
||||
String versionIdPart = Long.toString(theVersionIdPart);
|
||||
id.setParts(null, myResourceName, theIdPart, versionIdPart);
|
||||
if (theResource != null) {
|
||||
theResource.setId(id);
|
||||
}
|
||||
|
||||
TreeMap<Long, T> versionToResource = getVersionToResource(theIdPart);
|
||||
versionToResource.put(theVersionIdPart, theResource);
|
||||
/*
|
||||
* This is a bit of magic to make sure that the versionId attribute
|
||||
* in the resource being stored accurately represents the version
|
||||
* that was assigned by this provider
|
||||
*/
|
||||
if (theResource != null) {
|
||||
if (myFhirContext.getVersion().getVersion() == FhirVersionEnum.DSTU2) {
|
||||
ResourceMetadataKeyEnum.VERSION.put((IResource) theResource, versionIdPart);
|
||||
} else {
|
||||
BaseRuntimeChildDefinition metaChild = myFhirContext.getResourceDefinition(myResourceType).getChildByName("meta");
|
||||
List<IBase> metaValues = metaChild.getAccessor().getValues(theResource);
|
||||
if (metaValues.size() > 0) {
|
||||
IBase meta = metaValues.get(0);
|
||||
BaseRuntimeElementCompositeDefinition<?> metaDef = (BaseRuntimeElementCompositeDefinition<?>) myFhirContext.getElementDefinition(meta.getClass());
|
||||
BaseRuntimeChildDefinition versionIdDef = metaDef.getChildByName("versionId");
|
||||
List<IBase> versionIdValues = versionIdDef.getAccessor().getValues(meta);
|
||||
if (versionIdValues.size() > 0) {
|
||||
IPrimitiveType<?> versionId = (IPrimitiveType<?>) versionIdValues.get(0);
|
||||
versionId.setValueAsString(versionIdPart);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ourLog.info("Storing resource with ID: {}", id.getValue());
|
||||
|
||||
// Store to ID->version->resource map
|
||||
TreeMap<Long, T> versionToResource = getVersionToResource(theIdPart);
|
||||
versionToResource.put(theVersionIdPart, theResource);
|
||||
|
||||
// Store to type history map
|
||||
myTypeHistory.addFirst(theResource);
|
||||
|
||||
// Store to ID history map
|
||||
if (!myIdToHistory.containsKey(theIdPart)) {
|
||||
myIdToHistory.put(theIdPart, new LinkedList<>());
|
||||
}
|
||||
myIdToHistory.get(theIdPart).addFirst(theResource);
|
||||
|
||||
// Return the newly assigned ID including the version ID
|
||||
return id;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class SimpleBundleProviderTest {
|
||||
|
||||
@Test
|
||||
public void testPreferredPageSize() {
|
||||
SimpleBundleProvider p = new SimpleBundleProvider();
|
||||
assertEquals(null, p.preferredPageSize());
|
||||
|
||||
p.setPreferredPageSize(100);
|
||||
assertEquals(100, p.preferredPageSize().intValue());
|
||||
}
|
||||
|
||||
}
|
|
@ -49,7 +49,7 @@ public class FhirR4 implements IFhirVersion {
|
|||
|
||||
@Override
|
||||
public IContextValidationSupport<?, ?, ?, ?, ?, ?> createValidationSupport() {
|
||||
return ReflectionUtil.newInstanceOfFhirProfileValidationSupport("org.hl7.fhir.r4.hapi.validation.DefaultProfileValidationSupport");
|
||||
return ReflectionUtil.newInstanceOfFhirProfileValidationSupport("org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -123,9 +123,9 @@ public class R4BundleFactory implements IVersionSpecificBundleFactory {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Actually add the resources to the bundle
|
||||
*/
|
||||
/*
|
||||
* Actually add the resources to the bundle
|
||||
*/
|
||||
for (IBaseResource next : includedResources) {
|
||||
BundleEntryComponent entry = myBundle.addEntry();
|
||||
entry.setResource((Resource) next).getSearch().setMode(SearchEntryMode.INCLUDE);
|
||||
|
@ -195,7 +195,7 @@ public class R4BundleFactory implements IVersionSpecificBundleFactory {
|
|||
includedResources.addAll(addedResourcesThisPass);
|
||||
|
||||
// Linked resources may themselves have linked resources
|
||||
references = new ArrayList<ResourceReferenceInfo>();
|
||||
references = new ArrayList<>();
|
||||
for (IAnyResource iResource : addedResourcesThisPass) {
|
||||
List<ResourceReferenceInfo> newReferences = myContext.newTerser().getAllResourceReferences(iResource);
|
||||
references.addAll(newReferences);
|
||||
|
@ -219,9 +219,9 @@ public class R4BundleFactory implements IVersionSpecificBundleFactory {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Actually add the resources to the bundle
|
||||
*/
|
||||
/*
|
||||
* Actually add the resources to the bundle
|
||||
*/
|
||||
for (IAnyResource next : includedResources) {
|
||||
BundleEntryComponent entry = myBundle.addEntry();
|
||||
entry.setResource((Resource) next).getSearch().setMode(SearchEntryMode.INCLUDE);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,174 @@
|
|||
package ca.uhn.fhir.rest.client;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.*;
|
||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
|
||||
import ca.uhn.fhir.rest.client.impl.BaseClient;
|
||||
import ca.uhn.fhir.rest.client.interceptor.ThreadLocalCapturingInterceptor;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import ca.uhn.fhir.util.VersionUtil;
|
||||
import javassist.tools.web.BadHttpRequest;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.io.input.ReaderInputStream;
|
||||
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.hl7.fhir.r4.model.*;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.StringReader;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ThreadLocalCapturingInterceptorR4Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ThreadLocalCapturingInterceptorR4Test.class);
|
||||
private static FhirContext ourCtx;
|
||||
private int myAnswerCount;
|
||||
private HttpClient myHttpClient;
|
||||
private HttpResponse myHttpResponse;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
myHttpClient = mock(HttpClient.class, new ReturnsDeepStubs());
|
||||
ourCtx.getRestfulClientFactory().setHttpClient(myHttpClient);
|
||||
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
|
||||
myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
|
||||
myAnswerCount = 0;
|
||||
System.setProperty(BaseClient.HAPI_CLIENT_KEEPRESPONSES, "true");
|
||||
}
|
||||
|
||||
private String expectedUserAgent() {
|
||||
return "HAPI-FHIR/" + VersionUtil.getVersion() + " (FHIR Client; FHIR " + FhirVersionEnum.R4.getFhirVersionString() + "/R4; apache)";
|
||||
}
|
||||
|
||||
private byte[] extractBodyAsByteArray(ArgumentCaptor<HttpUriRequest> capt) throws IOException {
|
||||
byte[] body = IOUtils.toByteArray(((HttpEntityEnclosingRequestBase) capt.getAllValues().get(0)).getEntity().getContent());
|
||||
return body;
|
||||
}
|
||||
|
||||
private String extractBodyAsString(ArgumentCaptor<HttpUriRequest> capt) throws IOException {
|
||||
String body = IOUtils.toString(((HttpEntityEnclosingRequestBase) capt.getAllValues().get(0)).getEntity().getContent(), "UTF-8");
|
||||
return body;
|
||||
}
|
||||
|
||||
private ArgumentCaptor<HttpUriRequest> prepareClientForSearchResponse() throws IOException {
|
||||
final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";
|
||||
|
||||
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_JSON + "; charset=UTF-8"));
|
||||
when(myHttpResponse.getEntity().getContent()).then(new Answer<InputStream>() {
|
||||
@Override
|
||||
public InputStream answer(InvocationOnMock theInvocation) {
|
||||
return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
|
||||
}
|
||||
});
|
||||
return capt;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccessfulSearch() throws Exception {
|
||||
final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";
|
||||
|
||||
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_JSON + "; charset=UTF-8"));
|
||||
when(myHttpResponse.getEntity().getContent()).then(new Answer<InputStream>() {
|
||||
@Override
|
||||
public InputStream answer(InvocationOnMock theInvocation) {
|
||||
return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
|
||||
}
|
||||
});
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
int idx = 0;
|
||||
|
||||
ThreadLocalCapturingInterceptor interceptor = new ThreadLocalCapturingInterceptor();
|
||||
interceptor.setBufferResponse(true);
|
||||
|
||||
client.registerInterceptor(interceptor);
|
||||
client.setEncoding(EncodingEnum.JSON);
|
||||
client.search()
|
||||
.forResource("Device")
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
|
||||
assertEquals("http://example.com/fhir/Device?_format=json", interceptor.getRequestForCurrentThread().getUri());
|
||||
assertEquals(200, interceptor.getResponseForCurrentThread().getStatus());
|
||||
assertEquals(msg, IOUtils.toString(interceptor.getResponseForCurrentThread().createReader()));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFailingSearch() throws Exception {
|
||||
final String msg = "BAD REQUEST";
|
||||
|
||||
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), 400, "Bad Request"));
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT_WITH_UTF8));
|
||||
when(myHttpResponse.getEntity().getContent()).then(new Answer<InputStream>() {
|
||||
@Override
|
||||
public InputStream answer(InvocationOnMock theInvocation) {
|
||||
return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
|
||||
}
|
||||
});
|
||||
|
||||
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
|
||||
int idx = 0;
|
||||
|
||||
ThreadLocalCapturingInterceptor interceptor = new ThreadLocalCapturingInterceptor();
|
||||
interceptor.setBufferResponse(true);
|
||||
|
||||
client.registerInterceptor(interceptor);
|
||||
client.setEncoding(EncodingEnum.JSON);
|
||||
try {
|
||||
client.search()
|
||||
.forResource("Device")
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
assertEquals("http://example.com/fhir/Device?_format=json", interceptor.getRequestForCurrentThread().getUri());
|
||||
assertEquals(400, interceptor.getResponseForCurrentThread().getStatus());
|
||||
assertEquals(msg, IOUtils.toString(interceptor.getResponseForCurrentThread().createReader()));
|
||||
}
|
||||
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() {
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
ourCtx = FhirContext.forR4();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -259,12 +259,14 @@ public class CreateR4Test {
|
|||
ourPort = PortUtil.findFreePort();
|
||||
ourServer = new Server(ourPort);
|
||||
|
||||
PatientProvider patientProvider = new PatientProvider();
|
||||
PatientProviderCreate patientProviderCreate = new PatientProviderCreate();
|
||||
PatientProviderRead patientProviderRead = new PatientProviderRead();
|
||||
PatientProviderSearch patientProviderSearch = new PatientProviderSearch();
|
||||
|
||||
ServletHandler proxyHandler = new ServletHandler();
|
||||
RestfulServer servlet = new RestfulServer(ourCtx);
|
||||
|
||||
servlet.setResourceProviders(patientProvider);
|
||||
servlet.setResourceProviders(patientProviderCreate, patientProviderRead, patientProviderSearch);
|
||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||
ourServer.setHandler(proxyHandler);
|
||||
|
@ -276,21 +278,7 @@ public class CreateR4Test {
|
|||
ourClient = builder.build();
|
||||
|
||||
}
|
||||
|
||||
public static class PatientProvider implements IResourceProvider {
|
||||
|
||||
@Create()
|
||||
public MethodOutcome create(@ResourceParam Patient theIdParam) {
|
||||
assertNull(theIdParam.getIdElement().getIdPart());
|
||||
theIdParam.setId("1");
|
||||
theIdParam.getMeta().setVersionId("1");
|
||||
return new MethodOutcome(new IdType("Patient", "1"), true).setOperationOutcome(ourReturnOo).setResource(theIdParam);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Patient> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
public static class PatientProviderRead implements IResourceProvider {
|
||||
|
||||
@Read()
|
||||
public MyPatientWithExtensions read(@IdParam IdType theIdParam) {
|
||||
|
@ -300,6 +288,35 @@ public class CreateR4Test {
|
|||
return p0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Patient> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PatientProviderCreate implements IResourceProvider {
|
||||
@Override
|
||||
public Class<Patient> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
@Create()
|
||||
public MethodOutcome create(@ResourceParam Patient theIdParam) {
|
||||
assertNull(theIdParam.getIdElement().getIdPart());
|
||||
theIdParam.setId("1");
|
||||
theIdParam.getMeta().setVersionId("1");
|
||||
return new MethodOutcome(new IdType("Patient", "1"), true).setOperationOutcome(ourReturnOo).setResource(theIdParam);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PatientProviderSearch implements IResourceProvider {
|
||||
|
||||
|
||||
@Override
|
||||
public Class<Patient> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
|
||||
|
||||
@Search
|
||||
public List<IBaseResource> search() {
|
||||
ArrayList<IBaseResource> retVal = new ArrayList<IBaseResource>();
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.annotation.*;
|
||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import com.google.common.base.Charsets;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
|
@ -15,65 +16,53 @@ 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.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import ca.uhn.fhir.rest.annotation.At;
|
||||
import ca.uhn.fhir.rest.annotation.History;
|
||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||
import ca.uhn.fhir.rest.annotation.Read;
|
||||
import ca.uhn.fhir.rest.annotation.Since;
|
||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class HistoryDstu2Test {
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class HistoryR4Test {
|
||||
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(HistoryR4Test.class);
|
||||
private static CloseableHttpClient ourClient;
|
||||
private static FhirContext ourCtx = FhirContext.forDstu2();
|
||||
private static FhirContext ourCtx = FhirContext.forR4();
|
||||
private static DateRangeParam ourLastAt;
|
||||
|
||||
private static InstantDt ourLastSince;
|
||||
private static InstantType ourLastSince;
|
||||
private static IPrimitiveType<Date> ourLastSince2;
|
||||
private static IPrimitiveType<String> ourLastSince3;
|
||||
private static IPrimitiveType<?> ourLastSince4;
|
||||
private static int ourPort;
|
||||
|
||||
private static Server ourServer;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
ourLastAt = null;
|
||||
ourLastSince = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSince() throws Exception {
|
||||
{
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/_history?_since=2005");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
|
||||
assertEquals(null, ourLastAt);
|
||||
assertEquals("2005", ourLastSince.getValueAsString());
|
||||
}
|
||||
ourLastSince2 = null;
|
||||
ourLastSince3 = null;
|
||||
ourLastSince4 = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAt() throws Exception {
|
||||
{
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/_history?_at=gt2001&_at=lt2005");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info(responseContent);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
}
|
||||
|
||||
assertEquals(ParamPrefixEnum.GREATERTHAN, ourLastAt.getLowerBound().getPrefix());
|
||||
assertEquals("2001", ourLastAt.getLowerBound().getValueAsString());
|
||||
|
@ -86,56 +75,79 @@ public class HistoryDstu2Test {
|
|||
public void testInstanceHistory() throws Exception {
|
||||
{
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/_history?_pretty=true");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
|
||||
ourLog.info(responseContent);
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
String responseContent;
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
|
||||
responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info(responseContent);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
}
|
||||
|
||||
Bundle bundle = ourCtx.newXmlParser().parseResource(Bundle.class, responseContent);
|
||||
assertEquals(2, bundle.getEntry().size());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/ih1/_history/1", bundle.getEntry().get(0).getResource().getId().getValue());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/ih1/_history/2", bundle.getEntry().get(1).getResource().getId().getValue());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/ih1/_history/1", bundle.getEntry().get(0).getResource().getId());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/ih1/_history/2", bundle.getEntry().get(1).getResource().getId());
|
||||
|
||||
}
|
||||
}private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(HistoryDstu2Test.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerHistory() throws Exception {
|
||||
{
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/_history");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
String responseContent;
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
|
||||
responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info(responseContent);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
}
|
||||
|
||||
Bundle bundle = ourCtx.newXmlParser().parseResource(Bundle.class, responseContent);
|
||||
assertEquals(2, bundle.getEntry().size());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/h1/_history/1", bundle.getEntry().get(0).getResource().getId().getValue());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/h1/_history/2", bundle.getEntry().get(1).getResource().getId().getValue());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/h1/_history/1", bundle.getEntry().get(0).getResource().getId());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/h1/_history/2", bundle.getEntry().get(1).getResource().getId());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSince() throws Exception {
|
||||
{
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/_history?_since=2005");
|
||||
String responseContent;
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
|
||||
responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info(responseContent);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
}
|
||||
|
||||
assertEquals(null, ourLastAt);
|
||||
assertEquals("2005", ourLastSince.getValueAsString());
|
||||
assertEquals("2005", ourLastSince2.getValueAsString());
|
||||
assertTrue(DateTimeType.class.equals(ourLastSince2.getClass()));
|
||||
assertEquals("2005", ourLastSince3.getValueAsString());
|
||||
assertTrue(StringType.class.equals(ourLastSince3.getClass()));
|
||||
assertEquals("2005", ourLastSince4.getValueAsString());
|
||||
assertTrue(StringType.class.equals(ourLastSince4.getClass()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTypeHistory() throws Exception {
|
||||
{
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/_history");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
String responseContent;
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
|
||||
responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info(responseContent);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
}
|
||||
|
||||
assertNull(ourLastAt);
|
||||
|
||||
Bundle bundle = ourCtx.newXmlParser().parseResource(Bundle.class, responseContent);
|
||||
assertEquals(2, bundle.getEntry().size());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/th1/_history/1", bundle.getEntry().get(0).getResource().getId().getValue());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/th1/_history/2", bundle.getEntry().get(1).getResource().getId().getValue());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/th1/_history/1", bundle.getEntry().get(0).getResource().getId());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/th1/_history/2", bundle.getEntry().get(1).getResource().getId());
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -147,14 +159,15 @@ public class HistoryDstu2Test {
|
|||
public void testVread() throws Exception {
|
||||
{
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/_history/456");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
String responseContent;
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
|
||||
responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info(responseContent);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
}
|
||||
|
||||
Patient bundle = ourCtx.newXmlParser().parseResource(Patient.class, responseContent);
|
||||
assertEquals("vread", bundle.getNameFirstRep().getFamilyFirstRep().getValue());
|
||||
assertEquals("vread", bundle.getNameFirstRep().getFamily());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,20 +204,27 @@ public class HistoryDstu2Test {
|
|||
public static class DummyPlainProvider {
|
||||
|
||||
@History
|
||||
public List<Patient> history(@Since InstantDt theSince, @At DateRangeParam theAt) {
|
||||
public List<Patient> history(@Since InstantType theSince,
|
||||
@Since IPrimitiveType<Date> theSince2,
|
||||
@Since IPrimitiveType<String> theSince3,
|
||||
@Since IPrimitiveType theSince4,
|
||||
@At DateRangeParam theAt) {
|
||||
ourLastAt = theAt;
|
||||
ourLastSince = theSince;
|
||||
ourLastSince2 = theSince2;
|
||||
ourLastSince3 = theSince3;
|
||||
ourLastSince4 = theSince4;
|
||||
|
||||
ArrayList<Patient> retVal = new ArrayList<Patient>();
|
||||
ArrayList<Patient> retVal = new ArrayList<>();
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("Patient/h1/_history/1");
|
||||
patient.addName().addFamily("history");
|
||||
patient.addName().setFamily("history");
|
||||
retVal.add(patient);
|
||||
|
||||
Patient patient2 = new Patient();
|
||||
patient2.setId("Patient/h1/_history/2");
|
||||
patient2.addName().addFamily("history");
|
||||
patient2.addName().setFamily("history");
|
||||
retVal.add(patient2);
|
||||
|
||||
return retVal;
|
||||
|
@ -220,17 +240,17 @@ public class HistoryDstu2Test {
|
|||
}
|
||||
|
||||
@History
|
||||
public List<Patient> instanceHistory(@IdParam IdDt theId) {
|
||||
ArrayList<Patient> retVal = new ArrayList<Patient>();
|
||||
public List<Patient> instanceHistory(@IdParam IdType theId) {
|
||||
ArrayList<Patient> retVal = new ArrayList<>();
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("Patient/ih1/_history/1");
|
||||
patient.addName().addFamily("history");
|
||||
patient.addName().setFamily("history");
|
||||
retVal.add(patient);
|
||||
|
||||
Patient patient2 = new Patient();
|
||||
patient2.setId("Patient/ih1/_history/2");
|
||||
patient2.addName().addFamily("history");
|
||||
patient2.addName().setFamily("history");
|
||||
retVal.add(patient2);
|
||||
|
||||
return retVal;
|
||||
|
@ -238,25 +258,25 @@ public class HistoryDstu2Test {
|
|||
|
||||
@History
|
||||
public List<Patient> typeHistory() {
|
||||
ArrayList<Patient> retVal = new ArrayList<Patient>();
|
||||
ArrayList<Patient> retVal = new ArrayList<>();
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("Patient/th1/_history/1");
|
||||
patient.addName().addFamily("history");
|
||||
patient.addName().setFamily("history");
|
||||
retVal.add(patient);
|
||||
|
||||
Patient patient2 = new Patient();
|
||||
patient2.setId("Patient/th1/_history/2");
|
||||
patient2.addName().addFamily("history");
|
||||
patient2.addName().setFamily("history");
|
||||
retVal.add(patient2);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Read(version = true)
|
||||
public Patient vread(@IdParam IdDt theId) {
|
||||
public Patient vread(@IdParam IdType theId) {
|
||||
Patient retVal = new Patient();
|
||||
retVal.addName().addFamily("vread");
|
||||
retVal.addName().setFamily("vread");
|
||||
retVal.setId(theId);
|
||||
return retVal;
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import ca.uhn.fhir.util.UrlUtil;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.config.SocketConfig;
|
||||
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.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.ArgumentMatchers.contains;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class PagingUsingNamedPagesR4Test {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(PagingUsingNamedPagesR4Test.class);
|
||||
private static CloseableHttpClient ourClient;
|
||||
private static FhirContext ourCtx = FhirContext.forR4();
|
||||
private static int ourPort;
|
||||
|
||||
private static Server ourServer;
|
||||
private static RestfulServer servlet;
|
||||
private IPagingProvider myPagingProvider;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
myPagingProvider = mock(IPagingProvider.class);
|
||||
servlet.setPagingProvider(myPagingProvider);
|
||||
ourNextBundleProvider = null;
|
||||
}
|
||||
|
||||
private Bundle executeAndReturnBundle(HttpGet httpGet, EncodingEnum theExpectEncoding) throws IOException {
|
||||
Bundle bundle;
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
ourLog.info(responseContent);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
EncodingEnum ct = EncodingEnum.forContentType(status.getEntity().getContentType().getValue().replaceAll(";.*", "").trim());
|
||||
assertEquals(theExpectEncoding, ct);
|
||||
assert ct != null;
|
||||
bundle = ct.newParser(ourCtx).parseResource(Bundle.class, responseContent);
|
||||
assertEquals(10, bundle.getEntry().size());
|
||||
}
|
||||
return bundle;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPagingLinksSanitizeBundleType() throws Exception {
|
||||
|
||||
List<IBaseResource> patients0 = createPatients(0, 9);
|
||||
BundleProviderWithNamedPages provider0 = new BundleProviderWithNamedPages(patients0, "SEARCHID0", "PAGEID0", 1000);
|
||||
provider0.setNextPageId("PAGEID1");
|
||||
when(myPagingProvider.retrieveResultList(eq("SEARCHID0"), eq("PAGEID0"))).thenReturn(provider0);
|
||||
|
||||
// Initial search
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "?_getpages=SEARCHID0&pageId=PAGEID0&_format=xml&_bundletype=FOO" + UrlUtil.escapeUrlParam("\""));
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
ourLog.info(responseContent);
|
||||
assertThat(responseContent, not(containsString("FOO\"")));
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
EncodingEnum ct = EncodingEnum.forContentType(status.getEntity().getContentType().getValue().replaceAll(";.*", "").trim());
|
||||
assert ct != null;
|
||||
Bundle bundle = EncodingEnum.XML.newParser(ourCtx).parseResource(Bundle.class, responseContent);
|
||||
assertEquals(10, bundle.getEntry().size());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testPaging() throws Exception {
|
||||
|
||||
List<IBaseResource> patients0 = createPatients(0, 9);
|
||||
BundleProviderWithNamedPages provider0 = new BundleProviderWithNamedPages(patients0, "SEARCHID0", "PAGEID0", 1000);
|
||||
provider0.setNextPageId("PAGEID1");
|
||||
when(myPagingProvider.retrieveResultList(eq("SEARCHID0"), eq("PAGEID0"))).thenReturn(provider0);
|
||||
|
||||
List<IBaseResource> patients1 = createPatients(10, 19);
|
||||
BundleProviderWithNamedPages provider1 = new BundleProviderWithNamedPages(patients1, "SEARCHID0", "PAGEID1", 1000);
|
||||
provider1.setPreviousPageId("PAGEID0");
|
||||
provider1.setNextPageId("PAGEID2");
|
||||
when(myPagingProvider.retrieveResultList(eq("SEARCHID0"), eq("PAGEID1"))).thenReturn(provider1);
|
||||
|
||||
List<IBaseResource> patients2 = createPatients(20, 29);
|
||||
BundleProviderWithNamedPages provider2 = new BundleProviderWithNamedPages(patients2, "SEARCHID0", "PAGEID2", 1000);
|
||||
provider2.setPreviousPageId("PAGEID1");
|
||||
when(myPagingProvider.retrieveResultList(eq("SEARCHID0"), eq("PAGEID2"))).thenReturn(provider2);
|
||||
|
||||
ourNextBundleProvider = provider0;
|
||||
|
||||
HttpGet httpGet;
|
||||
String linkSelf;
|
||||
String linkNext;
|
||||
String linkPrev;
|
||||
Bundle bundle;
|
||||
|
||||
// Initial search
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_format=xml");
|
||||
bundle = executeAndReturnBundle(httpGet, EncodingEnum.XML);
|
||||
linkSelf = bundle.getLink(Constants.LINK_SELF).getUrl();
|
||||
assertEquals("http://localhost:"+ourPort+"/Patient?_format=xml", linkSelf);
|
||||
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||
assertEquals("http://localhost:"+ourPort+"?_getpages=SEARCHID0&pageId=PAGEID1&_format=xml&_bundletype=searchset", linkNext);
|
||||
assertNull(bundle.getLink(Constants.LINK_PREVIOUS));
|
||||
|
||||
// Fetch the next page
|
||||
httpGet = new HttpGet(linkNext);
|
||||
bundle = executeAndReturnBundle(httpGet, EncodingEnum.XML);
|
||||
linkSelf = bundle.getLink(Constants.LINK_SELF).getUrl();
|
||||
assertEquals("http://localhost:"+ourPort+"?_getpages=SEARCHID0&pageId=PAGEID1&_format=xml&_bundletype=searchset", linkSelf);
|
||||
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||
assertEquals("http://localhost:"+ourPort+"?_getpages=SEARCHID0&pageId=PAGEID2&_format=xml&_bundletype=searchset", linkNext);
|
||||
linkPrev = bundle.getLink(Constants.LINK_PREVIOUS).getUrl();
|
||||
assertEquals("http://localhost:"+ourPort+"?_getpages=SEARCHID0&pageId=PAGEID0&_format=xml&_bundletype=searchset", linkPrev);
|
||||
|
||||
// Fetch the next page
|
||||
httpGet = new HttpGet(linkNext);
|
||||
bundle = executeAndReturnBundle(httpGet, EncodingEnum.XML);
|
||||
linkSelf = bundle.getLink(Constants.LINK_SELF).getUrl();
|
||||
assertEquals("http://localhost:"+ourPort+"?_getpages=SEARCHID0&pageId=PAGEID2&_format=xml&_bundletype=searchset", linkSelf);
|
||||
assertNull(bundle.getLink(Constants.LINK_NEXT));
|
||||
linkPrev = bundle.getLink(Constants.LINK_PREVIOUS).getUrl();
|
||||
assertEquals("http://localhost:"+ourPort+"?_getpages=SEARCHID0&pageId=PAGEID1&_format=xml&_bundletype=searchset", linkPrev);
|
||||
}
|
||||
|
||||
private List<IBaseResource> createPatients(int theLow, int theHigh) {
|
||||
List<IBaseResource> patients = new ArrayList<>();
|
||||
for (int id = theLow; id <= theHigh; id++) {
|
||||
Patient pt = new Patient();
|
||||
pt.setId("Patient/" + id);
|
||||
pt.addName().setFamily("FAM" + id);
|
||||
patients.add(pt);
|
||||
}
|
||||
return patients;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() throws Exception {
|
||||
ourServer.stop();
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
ourPort = PortUtil.findFreePort();
|
||||
ourServer = new Server(ourPort);
|
||||
|
||||
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
|
||||
|
||||
ServletHandler proxyHandler = new ServletHandler();
|
||||
servlet = new RestfulServer(ourCtx);
|
||||
servlet.setDefaultResponseEncoding(EncodingEnum.JSON);
|
||||
servlet.setPagingProvider(new FifoMemoryPagingProvider(10));
|
||||
|
||||
servlet.setResourceProviders(patientProvider);
|
||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||
ourServer.setHandler(proxyHandler);
|
||||
ourServer.start();
|
||||
|
||||
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
|
||||
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||
builder.setConnectionManager(connectionManager);
|
||||
builder.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(600000).build());
|
||||
ourClient = builder.build();
|
||||
|
||||
}
|
||||
|
||||
private static IBundleProvider ourNextBundleProvider;
|
||||
public static class DummyPatientResourceProvider implements IResourceProvider {
|
||||
|
||||
|
||||
@Override
|
||||
public Class<? extends IBaseResource> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Search()
|
||||
public IBundleProvider search() {
|
||||
IBundleProvider retVal = ourNextBundleProvider;
|
||||
Validate.notNull(retVal);
|
||||
ourNextBundleProvider = null;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -2,6 +2,7 @@ package ca.uhn.fhir.rest.server.provider;
|
|||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
|
@ -19,17 +20,19 @@ import org.junit.AfterClass;
|
|||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.matchesPattern;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class HashMapResourceProviderTest {
|
||||
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(HashMapResourceProviderTest.class);
|
||||
private static MyRestfulServer ourRestServer;
|
||||
private static Server ourListenerServer;
|
||||
private static IGenericClient ourClient;
|
||||
|
@ -100,6 +103,93 @@ public class HashMapResourceProviderTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHistoryInstance() {
|
||||
// Create Res 1
|
||||
Patient p = new Patient();
|
||||
p.setActive(true);
|
||||
IIdType id1 = ourClient.create().resource(p).execute().getId();
|
||||
assertThat(id1.getIdPart(), matchesPattern("[0-9]+"));
|
||||
assertEquals("1", id1.getVersionIdPart());
|
||||
|
||||
// Create Res 2
|
||||
p = new Patient();
|
||||
p.setActive(true);
|
||||
IIdType id2 = ourClient.create().resource(p).execute().getId();
|
||||
assertThat(id2.getIdPart(), matchesPattern("[0-9]+"));
|
||||
assertEquals("1", id2.getVersionIdPart());
|
||||
|
||||
// Update Res 2
|
||||
p = new Patient();
|
||||
p.setId(id2);
|
||||
p.setActive(false);
|
||||
id2 = ourClient.update().resource(p).execute().getId();
|
||||
assertThat(id2.getIdPart(), matchesPattern("[0-9]+"));
|
||||
assertEquals("2", id2.getVersionIdPart());
|
||||
|
||||
Bundle history = ourClient
|
||||
.history()
|
||||
.onInstance(id2.toUnqualifiedVersionless())
|
||||
.andReturnBundle(Bundle.class)
|
||||
.encodedJson()
|
||||
.prettyPrint()
|
||||
.execute();
|
||||
ourLog.debug(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(history));
|
||||
List<String> ids = history
|
||||
.getEntry()
|
||||
.stream()
|
||||
.map(t -> t.getResource().getIdElement().toUnqualified().getValue())
|
||||
.collect(Collectors.toList());
|
||||
assertThat(ids, contains(
|
||||
id2.toUnqualified().withVersion("2").getValue(),
|
||||
id2.toUnqualified().withVersion("1").getValue()
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHistoryType() {
|
||||
// Create Res 1
|
||||
Patient p = new Patient();
|
||||
p.setActive(true);
|
||||
IIdType id1 = ourClient.create().resource(p).execute().getId();
|
||||
assertThat(id1.getIdPart(), matchesPattern("[0-9]+"));
|
||||
assertEquals("1", id1.getVersionIdPart());
|
||||
|
||||
// Create Res 2
|
||||
p = new Patient();
|
||||
p.setActive(true);
|
||||
IIdType id2 = ourClient.create().resource(p).execute().getId();
|
||||
assertThat(id2.getIdPart(), matchesPattern("[0-9]+"));
|
||||
assertEquals("1", id2.getVersionIdPart());
|
||||
|
||||
// Update Res 2
|
||||
p = new Patient();
|
||||
p.setId(id2);
|
||||
p.setActive(false);
|
||||
id2 = ourClient.update().resource(p).execute().getId();
|
||||
assertThat(id2.getIdPart(), matchesPattern("[0-9]+"));
|
||||
assertEquals("2", id2.getVersionIdPart());
|
||||
|
||||
Bundle history = ourClient
|
||||
.history()
|
||||
.onType(Patient.class)
|
||||
.andReturnBundle(Bundle.class)
|
||||
.execute();
|
||||
List<String> ids = history
|
||||
.getEntry()
|
||||
.stream()
|
||||
.map(t -> t.getResource().getIdElement().toUnqualified().getValue())
|
||||
.collect(Collectors.toList());
|
||||
ourLog.info("Received IDs: {}", ids);
|
||||
assertThat(ids, contains(
|
||||
id2.toUnqualified().withVersion("2").getValue(),
|
||||
id2.toUnqualified().withVersion("1").getValue(),
|
||||
id1.toUnqualified().withVersion("1").getValue()
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchAll() {
|
||||
// Create
|
||||
|
@ -112,7 +202,11 @@ public class HashMapResourceProviderTest {
|
|||
}
|
||||
|
||||
// Search
|
||||
Bundle resp = ourClient.search().forResource("Patient").returnBundle(Bundle.class).execute();
|
||||
Bundle resp = ourClient
|
||||
.search()
|
||||
.forResource("Patient")
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
assertEquals(100, resp.getTotal());
|
||||
assertEquals(100, resp.getEntry().size());
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@
|
|||
Resource loading logic for the JPA server has been optimized to
|
||||
reduce the number of database round trips required when loading
|
||||
search results where many of the entries have a "forced ID" (an alphanumeric
|
||||
client-assigned reosurce ID). Thanks to Frank Tao for the pull
|
||||
client-assigned resource ID). Thanks to Frank Tao for the pull
|
||||
request!
|
||||
</action>
|
||||
<action type="add">
|
||||
|
@ -163,6 +163,82 @@
|
|||
"Repreentation: OperationOutcome" value.
|
||||
Thanks to Ana Maria Radu for the pul request!
|
||||
</action>
|
||||
<action type="add">
|
||||
The REST Server module now allows more than one Resource Provider
|
||||
(i.e. more than one implementation of IResourceProvider) to be registered
|
||||
to the RestfulServer for the same resource type. Previous versions of
|
||||
HAPI FHIR have always limited support to a single resource provider, but
|
||||
this limitation did not serve any purpose so it has been removed.
|
||||
</action>
|
||||
<action type="add">
|
||||
The HashMapResourceProvider now supports the type and
|
||||
instance history operations. In addition, the search method
|
||||
for the
|
||||
<![CDATA[<code>_id</code>]]> search parameter now has the
|
||||
search parameter marked as "required". This means that additional
|
||||
search methods can be added in subclasses without their intended
|
||||
searches being routed to the searchById method. Also, the resource
|
||||
map now uses a LinkedHashMap, so searches return a predictable
|
||||
order for unit tests.
|
||||
</action>
|
||||
<action type="fix">
|
||||
Fixed a bug when creating a custom search parameter in the JPA
|
||||
server: if the SearchParameter resource contained an invalid
|
||||
expression, create/update operations for the given resource would
|
||||
fail with a cryptic error. SearchParameter expressions are now
|
||||
validated upon storage, and the SearchParameter will be rejected
|
||||
if the expression can not be processed.
|
||||
</action>
|
||||
<action type="add">
|
||||
The generic client history operations (history-instance, history-type,
|
||||
and history-server) now support the
|
||||
<![CDATA[<code>_at</code>]]> parameter.
|
||||
</action>
|
||||
<action type="add">
|
||||
In the plain server, many resource provider method parameters may now
|
||||
use a generic
|
||||
<![CDATA[<code>IPrimitiveType<String></code>]]>
|
||||
or
|
||||
<![CDATA[<code>IPrimitiveType<Date></code>]]> at the
|
||||
parameter type. This is handy if you are trying to write code
|
||||
that works across versions of FHIR.
|
||||
</action>
|
||||
<action type="add">
|
||||
Several convenience methods have been added to the fluent/generic
|
||||
client interfaces. These methods allow the adding of a sort via a
|
||||
SortSpec object, as well as specifying search parameters via a plain
|
||||
Map of Strings.
|
||||
</action>
|
||||
<action type="add">
|
||||
A new client interceptor called ThreadLocalCapturingInterceptor has been
|
||||
added. This interceptor works the same way as CapturingInterceptor in that
|
||||
it captures requests and responses for later processing, but it uses
|
||||
a ThreadLocal object to store them in order to facilitate
|
||||
use in multithreaded environments.
|
||||
</action>
|
||||
<action type="add">
|
||||
A new constructor has been added to the client BasicAuthInterceptor
|
||||
allowing credentials to be specified in the form
|
||||
"username:password" as an alternate to specifying them as two
|
||||
discrete strings.
|
||||
</action>
|
||||
<action type="add">
|
||||
SimpleBundleProvider has been modified to optionally allow calling
|
||||
code to specify a search UUID, and a field to allow the preferred
|
||||
page size to be configured.
|
||||
</action>
|
||||
<action type="add">
|
||||
The JPA server search UUID column has been reduced in length from
|
||||
40 chars to 36, in order to align with the actual length of the
|
||||
generated UUIDs.
|
||||
</action>
|
||||
<action type="add">
|
||||
Plain servers using paging may now specify an ID/name for
|
||||
individual pages being returned, avoiding the need to
|
||||
respond to arbitrary offset/index requests from the server.
|
||||
In this mode, page links in search result bundles simply
|
||||
include the ID to the next page.
|
||||
</action>
|
||||
</release>
|
||||
<release version="3.4.0" date="2018-05-28">
|
||||
<action type="add">
|
||||
|
|
|
@ -337,6 +337,33 @@
|
|||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Using Named Pages">
|
||||
|
||||
<p>
|
||||
By default, the paging system uses parameters that are embedded into the
|
||||
page links for the start index and the page size. This is useful for servers that
|
||||
can retrieve arbitrary offsets within a search result. For example,
|
||||
if a given search can easily retrieve "items 5-10 from the given search", then
|
||||
the mechanism above works well.
|
||||
</p>
|
||||
<p>
|
||||
Another option is to use "named pages", meaning that each
|
||||
page is simply assigned an ID by the server, and the next/previous
|
||||
page is requested using this ID.
|
||||
</p>
|
||||
<p>
|
||||
In order to support named pages, the IPagingProvider must
|
||||
implement the
|
||||
<code>retrieveResultList(String theSearchId, String thePageId)</code>
|
||||
method.
|
||||
</p>
|
||||
<p>
|
||||
Then, individual search/history methods may return a
|
||||
<code>BundleProviderWithNamedPages</code> instead of a simple
|
||||
<code>IBundleProvider</code>.
|
||||
</p>
|
||||
</subsection>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue