Merge pull request #1524 from jamesagnew/ks-narrow-search-pointcut

Add support for Narrow Search Results to work in Bundle
This commit is contained in:
Ken Stevens 2019-10-03 13:47:59 -04:00 committed by GitHub
commit d73bb9b7c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 361 additions and 128 deletions

View File

@ -1,10 +1,11 @@
package ca.uhn.fhir.util;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.util.bundle.BundleEntryMutator;
import ca.uhn.fhir.util.bundle.BundleEntryParts;
import ca.uhn.fhir.util.bundle.EntryListAccumulator;
import ca.uhn.fhir.util.bundle.ModifiableBundleEntry;
import org.apache.commons.lang3.tuple.Pair;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBundle;
@ -14,6 +15,7 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -42,37 +44,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
*/
public class BundleUtil {
public static class BundleEntryParts {
private final RequestTypeEnum myRequestType;
private final IBaseResource myResource;
private final String myUrl;
private final String myConditionalUrl;
BundleEntryParts(RequestTypeEnum theRequestType, String theUrl, IBaseResource theResource, String theConditionalUrl) {
super();
myRequestType = theRequestType;
myUrl = theUrl;
myResource = theResource;
myConditionalUrl = theConditionalUrl;
}
public RequestTypeEnum getRequestType() {
return myRequestType;
}
public IBaseResource getResource() {
return myResource;
}
public String getConditionalUrl() {
return myConditionalUrl;
}
public String getUrl() {
return myUrl;
}
}
/**
* @return Returns <code>null</code> if the link isn't found or has no value
*/
@ -185,20 +156,25 @@ public class BundleUtil {
* Extract all of the resources from a given bundle
*/
public static List<BundleEntryParts> toListOfEntries(FhirContext theContext, IBaseBundle theBundle) {
List<BundleEntryParts> retVal = new ArrayList<>();
EntryListAccumulator entryListAccumulator = new EntryListAccumulator();
processEntries(theContext, theBundle, entryListAccumulator);
return entryListAccumulator.getList();
}
RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
BaseRuntimeElementCompositeDefinition<?> entryChildElem = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
public static void processEntries(FhirContext theContext, IBaseBundle theBundle, Consumer<ModifiableBundleEntry> theProcessor) {
RuntimeResourceDefinition bundleDef = theContext.getResourceDefinition(theBundle);
BaseRuntimeChildDefinition entryChildDef = bundleDef.getChildByName("entry");
List<IBase> entries = entryChildDef.getAccessor().getValues(theBundle);
BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource");
BaseRuntimeChildDefinition requestChild = entryChildElem.getChildByName("request");
BaseRuntimeElementCompositeDefinition<?> requestElem = (BaseRuntimeElementCompositeDefinition<?>) requestChild.getChildByName("request");
BaseRuntimeChildDefinition requestUrlChild = requestElem.getChildByName("url");
BaseRuntimeChildDefinition requestIfNoneExistChild = requestElem.getChildByName("ifNoneExist");
BaseRuntimeChildDefinition methodChild = requestElem.getChildByName("method");
BaseRuntimeElementCompositeDefinition<?> entryChildContentsDef = (BaseRuntimeElementCompositeDefinition<?>) entryChildDef.getChildByName("entry");
BaseRuntimeChildDefinition resourceChildDef = entryChildContentsDef.getChildByName("resource");
BaseRuntimeChildDefinition requestChildDef = entryChildContentsDef.getChildByName("request");
BaseRuntimeElementCompositeDefinition<?> requestChildContentsDef = (BaseRuntimeElementCompositeDefinition<?>) requestChildDef.getChildByName("request");
BaseRuntimeChildDefinition requestUrlChildDef = requestChildContentsDef.getChildByName("url");
BaseRuntimeChildDefinition requestIfNoneExistChildDef = requestChildContentsDef.getChildByName("ifNoneExist");
BaseRuntimeChildDefinition methodChildDef = requestChildContentsDef.getChildByName("method");
for (IBase nextEntry : entries) {
IBaseResource resource = null;
@ -206,15 +182,15 @@ public class BundleUtil {
RequestTypeEnum requestType = null;
String conditionalUrl = null;
for (IBase next : resourceChild.getAccessor().getValues(nextEntry)) {
resource = (IBaseResource) next;
for (IBase nextResource : resourceChildDef.getAccessor().getValues(nextEntry)) {
resource = (IBaseResource) nextResource;
}
for (IBase nextRequest : requestChild.getAccessor().getValues(nextEntry)) {
for (IBase nextUrl : requestUrlChild.getAccessor().getValues(nextRequest)) {
for (IBase nextRequest : requestChildDef.getAccessor().getValues(nextEntry)) {
for (IBase nextUrl : requestUrlChildDef.getAccessor().getValues(nextRequest)) {
url = ((IPrimitiveType<?>) nextUrl).getValueAsString();
}
for (IBase nextUrl : methodChild.getAccessor().getValues(nextRequest)) {
String methodString = ((IPrimitiveType<?>) nextUrl).getValueAsString();
for (IBase nextMethod : methodChildDef.getAccessor().getValues(nextRequest)) {
String methodString = ((IPrimitiveType<?>) nextMethod).getValueAsString();
if (isNotBlank(methodString)) {
requestType = RequestTypeEnum.valueOf(methodString);
}
@ -227,7 +203,7 @@ public class BundleUtil {
conditionalUrl = url != null && url.contains("?") ? url : null;
break;
case POST:
List<IBase> ifNoneExistReps = requestIfNoneExistChild.getAccessor().getValues(nextRequest);
List<IBase> ifNoneExistReps = requestIfNoneExistChildDef.getAccessor().getValues(nextRequest);
if (ifNoneExistReps.size() > 0) {
IPrimitiveType<?> ifNoneExist = (IPrimitiveType<?>) ifNoneExistReps.get(0);
conditionalUrl = ifNoneExist.getValueAsString();
@ -241,11 +217,10 @@ public class BundleUtil {
* All 3 might be null - That's ok because we still want to know the
* order in the original bundle.
*/
retVal.add(new BundleEntryParts(requestType, url, resource, conditionalUrl));
BundleEntryMutator mutator = new BundleEntryMutator(nextEntry, requestChildDef, requestChildContentsDef);
ModifiableBundleEntry entry = new ModifiableBundleEntry(new BundleEntryParts(requestType, url, resource, conditionalUrl), mutator);
theProcessor.accept(entry);
}
return retVal;
}
/**

View File

@ -8,6 +8,8 @@ import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import com.google.common.escape.Escaper;
import com.google.common.net.PercentEscaper;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import java.io.UnsupportedEncodingException;
@ -457,4 +459,23 @@ public class UrlUtil {
return theString;
}
public static List<NameValuePair> translateMatchUrl(String theMatchUrl) {
List<NameValuePair> parameters;
String matchUrl = theMatchUrl;
int questionMarkIndex = matchUrl.indexOf('?');
if (questionMarkIndex != -1) {
matchUrl = matchUrl.substring(questionMarkIndex + 1);
}
matchUrl = matchUrl.replace("|", "%7C");
matchUrl = matchUrl.replace("=>=", "=%3E%3D");
matchUrl = matchUrl.replace("=<=", "=%3C%3D");
matchUrl = matchUrl.replace("=>", "=%3E");
matchUrl = matchUrl.replace("=<", "=%3C");
if (matchUrl.contains(" ")) {
throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - URL is invalid (must not contain spaces)");
}
parameters = URLEncodedUtils.parse((matchUrl), Constants.CHARSET_UTF8, '&');
return parameters;
}
}

View File

@ -0,0 +1,28 @@
package ca.uhn.fhir.util.bundle;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.util.ParametersUtil;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
public class BundleEntryMutator {
private final IBase myEntry;
private final BaseRuntimeChildDefinition myRequestChildDef;
private final BaseRuntimeElementCompositeDefinition<?> myRequestChildContentsDef;
public BundleEntryMutator(IBase theEntry, BaseRuntimeChildDefinition theRequestChildDef, BaseRuntimeElementCompositeDefinition<?> theRequestChildContentsDef) {
myEntry = theEntry;
myRequestChildDef = theRequestChildDef;
myRequestChildContentsDef = theRequestChildContentsDef;
}
void setRequestUrl(FhirContext theFhirContext, String theRequestUrl) {
BaseRuntimeChildDefinition requestUrlChildDef = myRequestChildContentsDef.getChildByName("url");
IPrimitiveType<?> url = ParametersUtil.createUri(theFhirContext, theRequestUrl);
for (IBase nextRequest : myRequestChildDef.getAccessor().getValues(myEntry)) {
requestUrlChildDef.getMutator().addValue(nextRequest, url);
}
}
}

View File

@ -0,0 +1,35 @@
package ca.uhn.fhir.util.bundle;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import org.hl7.fhir.instance.model.api.IBaseResource;
public class BundleEntryParts {
private final RequestTypeEnum myRequestType;
private final IBaseResource myResource;
private final String myUrl;
private final String myConditionalUrl;
public BundleEntryParts(RequestTypeEnum theRequestType, String theUrl, IBaseResource theResource, String theConditionalUrl) {
super();
myRequestType = theRequestType;
myUrl = theUrl;
myResource = theResource;
myConditionalUrl = theConditionalUrl;
}
public RequestTypeEnum getRequestType() {
return myRequestType;
}
public IBaseResource getResource() {
return myResource;
}
public String getConditionalUrl() {
return myConditionalUrl;
}
public String getUrl() {
return myUrl;
}
}

View File

@ -0,0 +1,18 @@
package ca.uhn.fhir.util.bundle;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class EntryListAccumulator implements Consumer<ModifiableBundleEntry> {
private final List<BundleEntryParts> myList = new ArrayList<>();
@Override
public void accept(ModifiableBundleEntry theModifiableBundleEntry) {
myList.add(theModifiableBundleEntry.getBundleEntryParts());
}
public List<BundleEntryParts> getList() {
return myList;
}
}

View File

@ -0,0 +1,25 @@
package ca.uhn.fhir.util.bundle;
import ca.uhn.fhir.context.FhirContext;
public class ModifiableBundleEntry {
private final BundleEntryParts myBundleEntryParts;
private final BundleEntryMutator myBundleEntryMutator;
public ModifiableBundleEntry(BundleEntryParts theBundleEntryParts, BundleEntryMutator theBundleEntryMutator) {
myBundleEntryParts = theBundleEntryParts;
myBundleEntryMutator = theBundleEntryMutator;
}
BundleEntryParts getBundleEntryParts() {
return myBundleEntryParts;
}
public void setRequestUrl(FhirContext theFhirContext, String theRequestUrl) {
myBundleEntryMutator.setRequestUrl(theFhirContext, theRequestUrl);
}
public String getRequestUrl() {
return myBundleEntryParts.getUrl();
}
}

View File

@ -22,7 +22,6 @@ package ca.uhn.fhir.rest.client.method;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

View File

@ -24,7 +24,7 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.jpa.delete.DeleteConflictList;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.TagDefinition;
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
import ca.uhn.fhir.rest.server.servlet.ServletSubRequestDetails;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;

View File

@ -30,7 +30,7 @@ import ca.uhn.fhir.jpa.delete.DeleteConflictList;
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
import ca.uhn.fhir.rest.server.servlet.ServletSubRequestDetails;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.util.DeleteConflict;
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
@ -49,11 +49,11 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
import ca.uhn.fhir.rest.server.method.BaseResourceReturningMethodBinding;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.rest.server.util.ServletRequestUtil;
import ca.uhn.fhir.util.*;
import com.google.common.base.Charsets;
import com.google.common.collect.ArrayListMultimap;
import org.apache.commons.lang3.Validate;
import org.apache.http.NameValuePair;
import org.hibernate.Session;
import org.hibernate.internal.SessionImpl;
import org.hl7.fhir.dstu3.model.Bundle;
@ -402,37 +402,14 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
Integer originalOrder = originalRequestOrder.get(nextReqEntry);
BUNDLEENTRY nextRespEntry = myVersionAdapter.getEntries(response).get(originalOrder);
ServletSubRequestDetails requestDetails = new ServletSubRequestDetails(theRequestDetails);
requestDetails.setServletRequest(theRequestDetails.getServletRequest());
requestDetails.setRequestType(RequestTypeEnum.GET);
requestDetails.setServer(theRequestDetails.getServer());
String url = extractTransactionUrlOrThrowException(nextReqEntry, "GET");
int qIndex = url.indexOf('?');
ArrayListMultimap<String, String> paramValues = ArrayListMultimap.create();
requestDetails.setParameters(new HashMap<>());
if (qIndex != -1) {
String params = url.substring(qIndex);
List<NameValuePair> parameters = myMatchUrlService.translateMatchUrl(params);
for (NameValuePair next : parameters) {
paramValues.put(next.getName(), next.getValue());
}
for (Map.Entry<String, Collection<String>> nextParamEntry : paramValues.asMap().entrySet()) {
String[] nextValue = nextParamEntry.getValue().toArray(new String[nextParamEntry.getValue().size()]);
requestDetails.addParameter(nextParamEntry.getKey(), nextValue);
}
url = url.substring(0, qIndex);
}
if (url.length() > 0 && url.charAt(0) == '/') {
url = url.substring(1);
}
String transactionUrl = extractTransactionUrlOrThrowException(nextReqEntry, "GET");
requestDetails.setRequestPath(url);
requestDetails.setFhirServerBase(theRequestDetails.getFhirServerBase());
ServletSubRequestDetails requestDetails = ServletRequestUtil.getServletSubRequestDetails(theRequestDetails, transactionUrl, paramValues);
String url = requestDetails.getRequestPath();
theRequestDetails.getServer().populateRequestDetailsFromRequestPath(requestDetails, url);
BaseMethodBinding<?> method = theRequestDetails.getServer().determineResourceMethod(requestDetails, url);
if (method == null) {
throw new IllegalArgumentException("Unable to handle GET " + url);

View File

@ -22,12 +22,10 @@ package ca.uhn.fhir.jpa.subscription.dbcache;
import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingInterceptor;
import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionProvider;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

View File

@ -34,9 +34,9 @@ import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.collect.ArrayListMultimap;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -138,23 +138,7 @@ public class MatchUrlService {
}
public List<NameValuePair> translateMatchUrl(String theMatchUrl) {
List<NameValuePair> parameters;
String matchUrl = theMatchUrl;
int questionMarkIndex = matchUrl.indexOf('?');
if (questionMarkIndex != -1) {
matchUrl = matchUrl.substring(questionMarkIndex + 1);
}
matchUrl = matchUrl.replace("|", "%7C");
matchUrl = matchUrl.replace("=>=", "=%3E%3D");
matchUrl = matchUrl.replace("=<=", "=%3C%3D");
matchUrl = matchUrl.replace("=>", "=%3E");
matchUrl = matchUrl.replace("=<", "=%3C");
if (matchUrl.contains(" ")) {
throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - URL is invalid (must not contain spaces)");
}
parameters = URLEncodedUtils.parse((matchUrl), Constants.CHARSET_UTF8, '&');
return parameters;
return UrlUtil.translateMatchUrl(theMatchUrl);
}
@CoverageIgnore

View File

@ -1,4 +1,5 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
@ -31,6 +32,10 @@
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
</dependency>
<!--
Spring is added as an optional dependency just so that it

View File

@ -12,7 +12,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.BundleUtil.BundleEntryParts;
import ca.uhn.fhir.util.bundle.BundleEntryParts;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.annotations.VisibleForTesting;

View File

@ -23,19 +23,31 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.rest.api.QualifiedParamList;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter;
import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.rest.server.servlet.ServletSubRequestDetails;
import ca.uhn.fhir.rest.server.util.ServletRequestUtil;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.bundle.ModifiableBundleEntry;
import com.google.common.collect.ArrayListMultimap;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.function.Consumer;
/**
* This interceptor can be used to automatically narrow the scope of searches in order to
@ -60,7 +72,9 @@ import java.util.*;
*
* @see AuthorizationInterceptor
*/
public abstract class SearchNarrowingInterceptor extends InterceptorAdapter {
public class SearchNarrowingInterceptor {
private static final Logger ourLog = LoggerFactory.getLogger(SearchNarrowingInterceptor.class);
/**
* Subclasses should override this method to supply the set of compartments that
@ -79,10 +93,8 @@ public abstract class SearchNarrowingInterceptor extends InterceptorAdapter {
return null;
}
@Override
@Hook(Pointcut.SERVER_INCOMING_REQUEST_POST_PROCESSED)
public boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException {
// We don't support this operation type yet
Validate.isTrue(theRequestDetails.getRestOperationType() != RestOperationTypeEnum.SEARCH_SYSTEM);
@ -171,6 +183,48 @@ public abstract class SearchNarrowingInterceptor extends InterceptorAdapter {
return true;
}
@Hook(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED)
public void incomingRequestPreHandled(ServletRequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException {
if (theRequestDetails.getRestOperationType() != RestOperationTypeEnum.TRANSACTION) {
return;
}
IBaseBundle bundle = (IBaseBundle) theRequestDetails.getResource();
FhirContext ctx = theRequestDetails.getFhirContext();
BundleEntryUrlProcessor processor = new BundleEntryUrlProcessor(ctx, theRequestDetails, theRequest, theResponse);
BundleUtil.processEntries(ctx, bundle, processor);
}
private class BundleEntryUrlProcessor implements Consumer<ModifiableBundleEntry> {
private final FhirContext myFhirContext;
private final ServletRequestDetails myRequestDetails;
private final HttpServletRequest myRequest;
private final HttpServletResponse myResponse;
public BundleEntryUrlProcessor(FhirContext theFhirContext, ServletRequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) {
myFhirContext = theFhirContext;
myRequestDetails = theRequestDetails;
myRequest = theRequest;
myResponse = theResponse;
}
@Override
public void accept(ModifiableBundleEntry theModifiableBundleEntry) {
ArrayListMultimap<String, String> paramValues = ArrayListMultimap.create();
String url = theModifiableBundleEntry.getRequestUrl();
ServletSubRequestDetails subServletRequestDetails = ServletRequestUtil.getServletSubRequestDetails(myRequestDetails, url, paramValues);
BaseMethodBinding<?> method = subServletRequestDetails.getServer().determineResourceMethod(subServletRequestDetails, url);
RestOperationTypeEnum restOperationType = method.getRestOperationType();
subServletRequestDetails.setRestOperationType(restOperationType);
incomingRequestPostProcessed(subServletRequestDetails, myRequest, myResponse);
theModifiableBundleEntry.setRequestUrl(myFhirContext, ServletRequestUtil.extractUrl(subServletRequestDetails));
}
}
private void processResourcesOrCompartments(RequestDetails theRequestDetails, RuntimeResourceDefinition theResDef, HashMap<String, List<String>> theParameterToOrValues, Collection<String> theResourcesOrCompartments, boolean theAreCompartments) {
String lastCompartmentName = null;
String lastSearchParamName = null;

View File

@ -1,4 +1,4 @@
package ca.uhn.fhir.jpa.provider;
package ca.uhn.fhir.rest.server.servlet;
/*
* #%L
@ -25,8 +25,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
public class ServletSubRequestDetails extends ServletRequestDetails {
private Map<String, List<String>> myHeaders = new HashMap<>();

View File

@ -0,0 +1,64 @@
package ca.uhn.fhir.rest.server.util;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.rest.server.servlet.ServletSubRequestDetails;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.collect.ArrayListMultimap;
import org.apache.http.NameValuePair;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ServletRequestUtil {
public static ServletSubRequestDetails getServletSubRequestDetails(ServletRequestDetails theRequestDetails, String url, ArrayListMultimap<String, String> theParamValues) {
ServletSubRequestDetails requestDetails = new ServletSubRequestDetails(theRequestDetails);
requestDetails.setServletRequest(theRequestDetails.getServletRequest());
requestDetails.setRequestType(RequestTypeEnum.GET);
requestDetails.setServer(theRequestDetails.getServer());
int qIndex = url.indexOf('?');
requestDetails.setParameters(new HashMap<>());
if (qIndex != -1) {
String params = url.substring(qIndex);
List<NameValuePair> parameters = UrlUtil.translateMatchUrl(params);
for (NameValuePair next : parameters) {
theParamValues.put(next.getName(), next.getValue());
}
for (Map.Entry<String, Collection<String>> nextParamEntry : theParamValues.asMap().entrySet()) {
String[] nextValue = nextParamEntry.getValue().toArray(new String[nextParamEntry.getValue().size()]);
requestDetails.addParameter(nextParamEntry.getKey(), nextValue);
}
url = url.substring(0, qIndex);
}
if (url.length() > 0 && url.charAt(0) == '/') {
url = url.substring(1);
}
requestDetails.setRequestPath(url);
requestDetails.setFhirServerBase(theRequestDetails.getFhirServerBase());
theRequestDetails.getServer().populateRequestDetailsFromRequestPath(requestDetails, url);
return requestDetails;
}
public static String extractUrl(ServletRequestDetails theRequestDetails) {
StringBuilder b = new StringBuilder();
for (Map.Entry<String, String[]> next : theRequestDetails.getParameters().entrySet()) {
for (String nextValue : next.getValue()) {
if (b.length() == 0) {
b.append('?');
} else {
b.append('&');
}
b.append(UrlUtil.escapeUrlParam(next.getKey()));
b.append('=');
b.append(UrlUtil.escapeUrlParam(nextValue));
}
}
return theRequestDetails.getRequestPath() + b.toString();
}
}

View File

@ -15,6 +15,7 @@ import java.util.concurrent.TimeUnit;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.util.BundleUtil;
import org.apache.commons.io.IOUtils;
import org.apache.http.*;
import org.apache.http.client.entity.UrlEncodedFormEntity;

View File

@ -40,6 +40,8 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*;
import org.junit.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

View File

@ -5,6 +5,8 @@ import ca.uhn.fhir.model.api.IQueryParameterOr;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.annotation.OptionalParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.annotation.Transaction;
import ca.uhn.fhir.rest.annotation.TransactionParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
@ -22,14 +24,13 @@ import org.eclipse.jetty.servlet.ServletHolder;
import org.hamcrest.Matchers;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.r4.model.*;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@ -39,8 +40,11 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import ca.uhn.fhir.test.utilities.JettyUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SearchNarrowingInterceptorTest {
private static final Logger ourLog = LoggerFactory.getLogger(SearchNarrowingInterceptorTest.class);
private static String ourLastHitMethod;
private static FhirContext ourCtx;
@ -54,6 +58,8 @@ public class SearchNarrowingInterceptorTest {
private static Server ourServer;
private static IGenericClient ourClient;
private static AuthorizedList ourNextCompartmentList;
private static Bundle.BundleEntryRequestComponent ourLastBundleRequest;
@Before
public void before() {
@ -104,6 +110,25 @@ public class SearchNarrowingInterceptorTest {
assertThat(toStrings(ourLastPatientParam), Matchers.contains("Patient/123,Patient/456"));
}
@Test
public void testNarrowObservationsByPatientContext_ClientRequestedBundleNoParams() {
ourNextCompartmentList = new AuthorizedList().addCompartments("Patient/123", "Patient/456");
Bundle bundle = new Bundle();
bundle.setType(Bundle.BundleType.TRANSACTION);
bundle.addEntry().getRequest().setMethod(Bundle.HTTPVerb.GET).setUrl("Patient");
ourLog.info(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));
ourClient
.transaction()
.withBundle(bundle)
.execute();
assertEquals("transaction", ourLastHitMethod);
assertEquals("Patient?_id=" + URLEncoder.encode("Patient/123,Patient/456"), ourLastBundleRequest.getUrl());
}
/**
* Should not make any changes
*/
@ -274,6 +299,15 @@ public class SearchNarrowingInterceptorTest {
}
public static class DummySystemProvider {
@Transaction
public Bundle transaction(@TransactionParam Bundle theInput) {
ourLastHitMethod = "transaction";
ourLastBundleRequest = theInput.getEntry().get(0).getRequest();
return theInput;
}
}
private static class MySearchNarrowingInterceptor extends SearchNarrowingInterceptor {
@Override
protected AuthorizedList buildAuthorizedList(RequestDetails theRequestDetails) {
@ -298,10 +332,12 @@ public class SearchNarrowingInterceptorTest {
DummyPatientResourceProvider patProvider = new DummyPatientResourceProvider();
DummyObservationResourceProvider obsProv = new DummyObservationResourceProvider();
DummySystemProvider systemProv = new DummySystemProvider();
ServletHandler proxyHandler = new ServletHandler();
RestfulServer ourServlet = new RestfulServer(ourCtx);
ourServlet.setFhirContext(ourCtx);
ourServlet.registerProviders(systemProv);
ourServlet.setResourceProviders(patProvider, obsProv);
ourServlet.setPagingProvider(new FifoMemoryPagingProvider(100));
ourServlet.registerInterceptor(new MySearchNarrowingInterceptor());

View File

@ -1,13 +1,17 @@
package ca.uhn.fhir.util;
package ca.uhn.fhir.util.bundle;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.BundleUtil;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Patient;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
import java.util.function.Consumer;
import static org.junit.Assert.assertEquals;
public class BundleUtilTest {
@ -18,27 +22,27 @@ public class BundleUtilTest {
Bundle b = new Bundle();
b.getLinkOrCreate("prev").setUrl("http://bar");
b.getLinkOrCreate("next").setUrl("http://foo");
Assert.assertEquals("http://foo", BundleUtil.getLinkUrlOfType(ourCtx, b, "next"));
assertEquals("http://foo", BundleUtil.getLinkUrlOfType(ourCtx, b, "next"));
}
@Test
public void testGetLinkDoesntExist() {
Bundle b = new Bundle();
b.getLinkOrCreate("prev").setUrl("http://bar");
Assert.assertEquals(null, BundleUtil.getLinkUrlOfType(ourCtx, b, "next"));
assertEquals(null, BundleUtil.getLinkUrlOfType(ourCtx, b, "next"));
}
@Test
public void testGetTotal() {
Bundle b = new Bundle();
b.setTotal(999);
Assert.assertEquals(999, BundleUtil.getTotal(ourCtx, b).intValue());
assertEquals(999, BundleUtil.getTotal(ourCtx, b).intValue());
}
@Test
public void testGetTotalNull() {
Bundle b = new Bundle();
Assert.assertEquals(null, BundleUtil.getTotal(ourCtx, b));
assertEquals(null, BundleUtil.getTotal(ourCtx, b));
}
@Test
@ -48,9 +52,18 @@ public class BundleUtilTest {
bundle.addEntry(new Bundle.BundleEntryComponent().setResource(new Patient()));
}
List<Patient> list = BundleUtil.toListOfResourcesOfType(ourCtx, bundle, Patient.class);
Assert.assertEquals(5, list.size());
assertEquals(5, list.size());
}
@Test
public void testProcessEntries() {
Bundle bundle = new Bundle();
bundle.setType(Bundle.BundleType.TRANSACTION);
bundle.addEntry().getRequest().setMethod(Bundle.HTTPVerb.GET).setUrl("Observation");
Consumer<ModifiableBundleEntry> consumer = e -> e.setRequestUrl(ourCtx, e.getRequestUrl() + "?foo=bar");
BundleUtil.processEntries(ourCtx, bundle, consumer);
assertEquals("Observation?foo=bar", bundle.getEntryFirstRep().getRequest().getUrl());
}
@AfterClass
public static void afterClassClearContext() {