More updates
This commit is contained in:
parent
f03d6b7c22
commit
518092cbd4
|
@ -124,7 +124,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";
|
||||
|
@ -134,7 +134,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";
|
||||
|
@ -146,7 +146,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";
|
||||
|
@ -154,9 +154,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";
|
||||
|
@ -171,7 +171,7 @@ public class Constants {
|
|||
public static final int STATUS_HTTP_400_BAD_REQUEST = 400;
|
||||
public static final int STATUS_HTTP_401_CLIENT_UNAUTHORIZED = 401;
|
||||
public static final int STATUS_HTTP_403_FORBIDDEN = 403;
|
||||
|
||||
|
||||
public static final int STATUS_HTTP_404_NOT_FOUND = 404;
|
||||
public static final int STATUS_HTTP_405_METHOD_NOT_ALLOWED = 405;
|
||||
public static final int STATUS_HTTP_409_CONFLICT = 409;
|
||||
|
@ -190,6 +190,7 @@ public class Constants {
|
|||
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);
|
||||
|
@ -259,7 +260,7 @@ public class Constants {
|
|||
statusNames.put(510, "Not Extended");
|
||||
statusNames.put(511, "Network Authentication Required");
|
||||
HTTP_STATUS_NAMES = Collections.unmodifiableMap(statusNames);
|
||||
|
||||
|
||||
Set<String> formatsHtml = new HashSet<>();
|
||||
formatsHtml.add(CT_HTML);
|
||||
formatsHtml.add(FORMAT_HTML);
|
||||
|
|
|
@ -14,9 +14,9 @@ import java.util.Map;
|
|||
* 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.
|
||||
|
|
|
@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.gclient;
|
|||
* 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.
|
||||
|
|
|
@ -19,9 +19,9 @@ import java.util.Map;
|
|||
* 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.
|
||||
|
|
|
@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.gclient;
|
|||
* 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.
|
||||
|
|
|
@ -25,9 +25,9 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||
* 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.
|
||||
|
|
|
@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.server.exceptions;
|
|||
* 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.
|
||||
|
|
|
@ -9,9 +9,9 @@ package ca.uhn.fhir.util;
|
|||
* 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.
|
||||
|
|
|
@ -25,9 +25,9 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
|
|||
* 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.
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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,51 +26,89 @@ 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.
|
||||
*
|
||||
* @param theFromIndex
|
||||
* The low index (inclusive) to return
|
||||
* @param theToIndex
|
||||
* The high index (exclusive) to return
|
||||
* <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
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* implementation are expensive to load so a smaller page size should be used. The value
|
||||
* returned by this method will only be used if the client has not explicitly requested
|
||||
* a page size.
|
||||
*
|
||||
* @return Returns the preferred page size or <code>null</code>
|
||||
*/
|
||||
Integer preferredPageSize();
|
||||
|
||||
/**
|
||||
* Returns the total number of results which match the given query (exclusive of any
|
||||
* _include's or OperationOutcome). May return {@literal null} if the total size is not
|
||||
* known or would be too expensive to calculate.
|
||||
*/
|
||||
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
|
||||
|
@ -79,7 +121,29 @@ public interface IBundleProvider {
|
|||
* 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>
|
||||
*/
|
||||
public String getUuid();
|
||||
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
|
||||
* implementation are expensive to load so a smaller page size should be used. The value
|
||||
* returned by this method will only be used if the client has not explicitly requested
|
||||
* a page size.
|
||||
*
|
||||
* @return Returns the preferred page size or <code>null</code>
|
||||
*/
|
||||
Integer preferredPageSize();
|
||||
|
||||
/**
|
||||
* Returns the total number of results which match the given query (exclusive of any
|
||||
* _include's or OperationOutcome). May return {@literal null} if the total size is not
|
||||
* known or would be too expensive to calculate.
|
||||
*/
|
||||
Integer size();
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -25,17 +25,24 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
|||
public interface IPagingProvider {
|
||||
|
||||
int getDefaultPageSize();
|
||||
|
||||
|
||||
int getMaximumPageSize();
|
||||
|
||||
/**
|
||||
* Stores a result list and returns an ID with which that list can be returned
|
||||
*/
|
||||
public String storeResultList(IBundleProvider theList);
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve a result list by ID
|
||||
*/
|
||||
public IBundleProvider retrieveResultList(String theId);
|
||||
|
||||
IBundleProvider retrieveResultList(String theSearchId);
|
||||
|
||||
/**
|
||||
* Retrieve a result list by ID
|
||||
*/
|
||||
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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.server;
|
|||
* 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.
|
||||
|
@ -23,8 +23,10 @@ package ca.uhn.fhir.rest.server;
|
|||
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 {
|
||||
|
@ -32,32 +34,48 @@ public class SimpleBundleProvider implements IBundleProvider {
|
|||
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) {
|
||||
this(theList, null);
|
||||
}
|
||||
|
||||
public SimpleBundleProvider(IBaseResource theResource) {
|
||||
myList = Collections.singletonList(theResource);
|
||||
myUuid = null;
|
||||
this(Collections.singletonList(theResource));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an empty bundle
|
||||
*/
|
||||
public SimpleBundleProvider() {
|
||||
myList = Collections.emptyList();
|
||||
myUuid = null;
|
||||
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 InstantDt getPublished() {
|
||||
return InstantDt.withCurrentTime();
|
||||
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
|
||||
|
@ -86,9 +104,18 @@ public class SimpleBundleProvider implements IBundleProvider {
|
|||
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 Integer size() {
|
||||
return myList.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
|
||||
|
@ -128,7 +128,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public IBundleProvider invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
|
||||
|
@ -139,18 +139,33 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
Object response = invokeServerMethod(theServer, theRequest, theMethodParams);
|
||||
|
||||
final IBundleProvider resources = toResourceList(response);
|
||||
|
||||
|
||||
/*
|
||||
* We wrap the response so we can verify that it has the ID and version set,
|
||||
* as is the contract for history
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
|
||||
List<IBaseResource> retVal = resources.getResources(theFromIndex, theToIndex);
|
||||
|
@ -170,10 +185,10 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
@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,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) {
|
||||
|
@ -125,7 +149,14 @@ public class PageMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
if (responseEncoding != null) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.server.provider;
|
|||
* 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.
|
||||
|
|
|
@ -9,9 +9,9 @@ package org.hl7.fhir.r4.hapi.rest.server;
|
|||
* 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.
|
||||
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -214,6 +214,18 @@
|
|||
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">
|
||||
|
|
|
@ -336,7 +336,34 @@
|
|||
</macro>
|
||||
|
||||
</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